diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000000..b64d4ccedf --- /dev/null +++ b/LICENSE @@ -0,0 +1,18 @@ +Tangem Proprietary and Confidential +Copyright (c) 2025 Tangem AG. All rights reserved. +================================================================================ +NOTICE: THIS IS NOT AN OPEN SOURCE LICENSE. +This software, including all source code, documentation, and accompanying files (the "Software") stored in this repository, is the proprietary and confidential information of Tangem AG ("Tangem"). The publication of this Software on a public platform does not grant any license, express or implied. +1. Definitions. +"Software" refers to all contents of this repository. +"Protocol" refers to the proprietary communication protocol, including the sequence of, data structures, and cryptographic methods used to interact with Tangem hardware. +"SDKs" refer to the official Software Development Kits (e.g., for Android, iOS, React Native) provided by Tangem in separate repositories and under their own distinct licenses. +2. Strict Prohibition of Use. Any use of the Software, in whole or in part, is strictly prohibited without the express prior written consent of Tangem. This prohibition includes, but is not limited to, the following actions for any purpose, whether commercial or non-commercial: +- Copying, modifying, or merging the Software. +- Publishing, distributing, or sublicensing the Software. +- Selling copies of the Software. +- Creating derivative works based on the Software. +3. Protocol Usage. Reverse-engineering, analyzing, decompiling, or attempting to recreate the Protocol is strictly prohibited. The only authorized method for third-party applications to interact with Tangem hardware is by using the official Tangem SDKs. The SDKs are governed by their own license terms (e.g., MIT License), which permit their use in third-party applications. This Software is provided for transparency and reference purposes only. +4. No Implied Rights. The act of Tangem publishing the Software to a public repository does not grant users any implied rights or licenses to use, modify, or distribute the Software. All rights not expressly granted by Tangem in a separate written agreement are reserved. +5. Legal Action. Unauthorized use, reproduction, or distribution of the Software or the Protocol may result in severe civil and criminal penalties, and will be prosecuted to the maximum extent possible under applicable law. +For inquiries about licensing or permissions to use the Software or the Protocol, please contact Tangem AG at: legal@tangem.com. diff --git a/app/src/main/java/com/tangem/tap/di/domain/ManageTokensDomainModule.kt b/app/src/main/java/com/tangem/tap/di/domain/ManageTokensDomainModule.kt index d89dd6d8ce..55d82f0af5 100644 --- a/app/src/main/java/com/tangem/tap/di/domain/ManageTokensDomainModule.kt +++ b/app/src/main/java/com/tangem/tap/di/domain/ManageTokensDomainModule.kt @@ -7,8 +7,6 @@ import com.tangem.domain.managetokens.repository.ManageTokensRepository import com.tangem.domain.networks.multi.MultiNetworkStatusFetcher import com.tangem.domain.quotes.multi.MultiQuoteStatusFetcher import com.tangem.domain.staking.multi.MultiYieldBalanceFetcher -import com.tangem.domain.tokens.MultiWalletCryptoCurrenciesSupplier -import com.tangem.domain.tokens.TokensFeatureToggles import com.tangem.domain.tokens.repository.CurrenciesRepository import com.tangem.domain.walletmanager.WalletManagersFacade import com.tangem.utils.coroutines.CoroutineDispatcherProvider @@ -74,8 +72,6 @@ internal object ManageTokensDomainModule { multiNetworkStatusFetcher: MultiNetworkStatusFetcher, multiQuoteStatusFetcher: MultiQuoteStatusFetcher, multiYieldBalanceFetcher: MultiYieldBalanceFetcher, - multiWalletCryptoCurrenciesSupplier: MultiWalletCryptoCurrenciesSupplier, - tokensFeatureToggles: TokensFeatureToggles, ): SaveManagedTokensUseCase { return SaveManagedTokensUseCase( customTokensRepository = customTokensRepository, @@ -85,8 +81,6 @@ internal object ManageTokensDomainModule { multiNetworkStatusFetcher = multiNetworkStatusFetcher, multiQuoteStatusFetcher = multiQuoteStatusFetcher, multiYieldBalanceFetcher = multiYieldBalanceFetcher, - multiWalletCryptoCurrenciesSupplier = multiWalletCryptoCurrenciesSupplier, - tokensFeatureToggles = tokensFeatureToggles, ) } diff --git a/app/src/main/java/com/tangem/tap/di/domain/WalletsDomainModule.kt b/app/src/main/java/com/tangem/tap/di/domain/WalletsDomainModule.kt index 26467ac00e..87b107936a 100644 --- a/app/src/main/java/com/tangem/tap/di/domain/WalletsDomainModule.kt +++ b/app/src/main/java/com/tangem/tap/di/domain/WalletsDomainModule.kt @@ -214,8 +214,8 @@ internal object WalletsDomainModule { @Singleton fun providesGetSavedWalletChangesIdUseCase( userWalletsListManager: UserWalletsListManager, - ): GetSavedWalletChangesUseCase { - return GetSavedWalletChangesUseCase( + ): GetSavedWalletsCountUseCase { + return GetSavedWalletsCountUseCase( userWalletsListManager = userWalletsListManager, ) } diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/BiometricUserWalletsListManager.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/BiometricUserWalletsListManager.kt index f67330ee89..0d5e883d53 100644 --- a/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/BiometricUserWalletsListManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/BiometricUserWalletsListManager.kt @@ -36,6 +36,11 @@ internal class BiometricUserWalletsListManager( .mapLatest { it.userWallets } .distinctUntilChanged() + override val savedWalletsCount: Flow + get() = state + .mapLatest { walletsCount } + .distinctUntilChanged() + override val userWalletsSync: List get() = state.value.userWallets diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/GeneralUserWalletsListManager.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/GeneralUserWalletsListManager.kt index 4cad89ba5d..1be31c95b6 100644 --- a/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/GeneralUserWalletsListManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/GeneralUserWalletsListManager.kt @@ -60,6 +60,14 @@ internal class GeneralUserWalletsListManager( // As a result subscription occurs on empty flow, than will not change if user wallets are available .filter { requireImplementation.hasUserWallets } + override val savedWalletsCount: Flow + get() = implementation + .transformLatest { impl -> + if (impl != null) { + emitAll(impl.savedWalletsCount) + } + } + override val userWalletsSync: List get() = requireImplementation.userWalletsSync diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/RuntimeUserWalletsListManager.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/RuntimeUserWalletsListManager.kt index 4d211d9b88..43bd7646e0 100644 --- a/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/RuntimeUserWalletsListManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/RuntimeUserWalletsListManager.kt @@ -20,6 +20,11 @@ internal class RuntimeUserWalletsListManager : UserWalletsListManager { .mapLatest { listOfNotNull(it.userWallet) } .distinctUntilChanged() + override val savedWalletsCount: Flow + get() = state + .mapLatest { walletsCount } + .distinctUntilChanged() + override val selectedUserWallet: Flow get() = state .mapLatest { it.userWallet } diff --git a/app/src/main/java/com/tangem/tap/features/main/MainViewModel.kt b/app/src/main/java/com/tangem/tap/features/main/MainViewModel.kt index 2991faccbd..50db2f2e66 100644 --- a/app/src/main/java/com/tangem/tap/features/main/MainViewModel.kt +++ b/app/src/main/java/com/tangem/tap/features/main/MainViewModel.kt @@ -41,7 +41,7 @@ import com.tangem.domain.staking.FetchStakingTokensUseCase import com.tangem.domain.wallets.legacy.UserWalletsListManager import com.tangem.domain.wallets.models.requireColdWallet import com.tangem.domain.wallets.usecase.AssociateWalletsWithApplicationIdUseCase -import com.tangem.domain.wallets.usecase.GetSavedWalletChangesUseCase +import com.tangem.domain.wallets.usecase.GetSavedWalletsCountUseCase import com.tangem.domain.wallets.usecase.UpdateRemoteWalletsInfoUseCase import com.tangem.feature.swap.analytics.StoriesEvents import com.tangem.tap.common.extensions.setContext @@ -84,7 +84,7 @@ internal class MainViewModel @Inject constructor( private val onboardingRepository: OnboardingRepository, private val notificationsToggles: NotificationsFeatureToggles, private val getApplicationIdUseCase: GetApplicationIdUseCase, - private val subscribeOnWalletsUseCase: GetSavedWalletChangesUseCase, + private val subscribeOnWalletsUseCase: GetSavedWalletsCountUseCase, private val associateWalletsWithApplicationIdUseCase: AssociateWalletsWithApplicationIdUseCase, private val updateRemoteWalletsInfoUseCase: UpdateRemoteWalletsInfoUseCase, private val sendPushTokenUseCase: SendPushTokenUseCase, diff --git a/core/datasource/src/main/java/com/tangem/datasource/info/AndroidAppInfoProvider.kt b/core/datasource/src/main/java/com/tangem/datasource/info/AndroidAppInfoProvider.kt index 08d960e333..0bc5462596 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/info/AndroidAppInfoProvider.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/info/AndroidAppInfoProvider.kt @@ -1,7 +1,6 @@ package com.tangem.datasource.info import android.os.Build -import com.tangem.utils.SupportedLanguages import com.tangem.utils.info.AppInfoProvider import com.tangem.utils.version.AppVersionProvider import java.util.* @@ -17,7 +16,7 @@ internal class AndroidAppInfoProvider @Inject constructor( override val osVersion: String get() = Build.VERSION.RELEASE override val language: String - get() = SupportedLanguages.getCurrentSupportedLanguageCode() + get() = Locale.getDefault().language override val timezone: String get() = TimeZone.getDefault().id override val appVersion: String diff --git a/data/notifications/src/main/java/com/tangem/data/notifications/DefaultNotificationsRepository.kt b/data/notifications/src/main/java/com/tangem/data/notifications/DefaultNotificationsRepository.kt index 661b93086d..d1f92e8262 100644 --- a/data/notifications/src/main/java/com/tangem/data/notifications/DefaultNotificationsRepository.kt +++ b/data/notifications/src/main/java/com/tangem/data/notifications/DefaultNotificationsRepository.kt @@ -68,6 +68,10 @@ internal class DefaultNotificationsRepository @Inject constructor( appId.value, NotificationApplicationCreateBody( pushToken = pushToken, + systemVersion = appInfoProvider.osVersion, + language = appInfoProvider.language, + timezone = appInfoProvider.timezone, + version = appInfoProvider.appVersion, ), ).getOrThrow() } diff --git a/data/notifications/src/test/java/com/tangem/data/notifications/DefaultNotificationsRepositoryTest.kt b/data/notifications/src/test/java/com/tangem/data/notifications/DefaultNotificationsRepositoryTest.kt index 7602280459..13d4dbb6b1 100644 --- a/data/notifications/src/test/java/com/tangem/data/notifications/DefaultNotificationsRepositoryTest.kt +++ b/data/notifications/src/test/java/com/tangem/data/notifications/DefaultNotificationsRepositoryTest.kt @@ -111,16 +111,20 @@ class DefaultNotificationsRepositoryTest { // GIVEN val appId = ApplicationId("test-app-id") val pushToken = "test-push-token" + coEvery { appInfoProvider.device } returns "test-device" + coEvery { appInfoProvider.osVersion } returns "11" + coEvery { appInfoProvider.language } returns "en" + coEvery { appInfoProvider.appVersion } returns "5.21.1" + coEvery { appInfoProvider.timezone } returns "UTC" coEvery { tangemTechApi.updatePushTokenForApplicationId( appId.value, NotificationApplicationCreateBody( pushToken = pushToken, - platform = null, - device = null, - systemVersion = null, - language = null, - timezone = null, + systemVersion = "11", + language = "en", + timezone = "UTC", + version = "5.21.1", ), ) } returns ApiResponse.Success(Unit) @@ -134,11 +138,10 @@ class DefaultNotificationsRepositoryTest { appId.value, NotificationApplicationCreateBody( pushToken = pushToken, - platform = null, - device = null, - systemVersion = null, - language = null, - timezone = null, + systemVersion = "11", + language = "en", + timezone = "UTC", + version = "5.21.1", ), ) } 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 6d25a6c018..3f79af5f36 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 @@ -129,6 +129,61 @@ internal class DefaultCurrenciesRepository( currenciesToAdd } + override suspend fun saveNewCurrenciesListCache(userWalletId: UserWalletId, currencies: List) { + withContext(dispatchers.io) { + val savedResponse = requireNotNull( + value = getSavedUserTokensResponseSync(key = userWalletId), + lazyMessage = { "Saved tokens empty. Can not perform add currencies action." }, + ) + + val newCurrencies = populateCurrenciesWithMissedCoins(currencies) + + val updatedResponse = savedResponse.copy( + tokens = newCurrencies.map(userTokensResponseFactory::createResponseToken), + ) + userTokensSaver.store( + userWalletId = userWalletId, + response = updatedResponse, + ) + + fetchExpressAssetsByNetworkIds( + userWallet = userWalletsStore.getSyncStrict(key = userWalletId), + userTokens = updatedResponse, + ) + } + } + + override suspend fun addCurrenciesCache( + 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 = filterAlreadyAddedCurrencies( + savedCurrencies = savedCurrencies.tokens, + currenciesToAdd = populateCurrenciesWithMissedCoins(currencies = currencies), + ) + + val updatedResponse = savedCurrencies.copy( + tokens = savedCurrencies.tokens + currenciesToAdd.map(userTokensResponseFactory::createResponseToken), + ) + + userTokensSaver.store( + userWalletId = userWalletId, + response = updatedResponse, + ) + + fetchExpressAssetsByNetworkIds( + userWallet = userWalletsStore.getSyncStrict(key = userWalletId), + userTokens = updatedResponse, + ) + + currenciesToAdd + } + private fun filterAlreadyAddedCurrencies( savedCurrencies: List, currenciesToAdd: List, @@ -605,17 +660,16 @@ internal class DefaultCurrenciesRepository( } override suspend fun syncTokens(userWalletId: UserWalletId) { - val savedCurrencies = requireNotNull( - value = getSavedUserTokensResponseSync(key = userWalletId), - lazyMessage = { "Saved tokens empty. Can not perform add currencies action" }, - ) - userTokensSaver.push( - userWalletId = userWalletId, - response = savedCurrencies, - onFailSend = { - throw IllegalStateException("Unable to push tokens") - }, - ) + runCatching { + val savedCurrencies = requireNotNull( + value = getSavedUserTokensResponseSync(key = userWalletId), + lazyMessage = { "Saved tokens empty. Can not perform add currencies action" }, + ) + userTokensSaver.storeAndPush( + userWalletId = userWalletId, + response = savedCurrencies, + ) + } } override fun getCardTypesResolver(userWalletId: UserWalletId): CardTypesResolver? { diff --git a/domain/manage-tokens/src/main/kotlin/com/tangem/domain/managetokens/SaveManagedTokensUseCase.kt b/domain/manage-tokens/src/main/kotlin/com/tangem/domain/managetokens/SaveManagedTokensUseCase.kt index 9e00d534b7..20bf0decd0 100644 --- a/domain/manage-tokens/src/main/kotlin/com/tangem/domain/managetokens/SaveManagedTokensUseCase.kt +++ b/domain/manage-tokens/src/main/kotlin/com/tangem/domain/managetokens/SaveManagedTokensUseCase.kt @@ -10,9 +10,6 @@ import com.tangem.domain.models.network.Network import com.tangem.domain.networks.multi.MultiNetworkStatusFetcher import com.tangem.domain.quotes.multi.MultiQuoteStatusFetcher import com.tangem.domain.staking.multi.MultiYieldBalanceFetcher -import com.tangem.domain.tokens.MultiWalletCryptoCurrenciesProducer -import com.tangem.domain.tokens.MultiWalletCryptoCurrenciesSupplier -import com.tangem.domain.tokens.TokensFeatureToggles import com.tangem.domain.tokens.repository.CurrenciesRepository import com.tangem.domain.walletmanager.WalletManagersFacade import com.tangem.domain.wallets.models.UserWalletId @@ -26,8 +23,6 @@ class SaveManagedTokensUseCase( private val multiNetworkStatusFetcher: MultiNetworkStatusFetcher, private val multiQuoteStatusFetcher: MultiQuoteStatusFetcher, private val multiYieldBalanceFetcher: MultiYieldBalanceFetcher, - private val multiWalletCryptoCurrenciesSupplier: MultiWalletCryptoCurrenciesSupplier, - private val tokensFeatureToggles: TokensFeatureToggles, ) { suspend operator fun invoke( @@ -35,43 +30,33 @@ class SaveManagedTokensUseCase( currenciesToAdd: Map>, currenciesToRemove: Map>, ): Either = Either.catch { - val removingCurrencies = currenciesToRemove.mapToCryptoCurrencies(userWalletId) - val addingCurrencies = currenciesToAdd.mapToCryptoCurrencies(userWalletId) + if (currenciesToRemove.isNotEmpty()) { + val removingCurrencies = currenciesToRemove.mapToCryptoCurrencies(userWalletId) - derivationsRepository.derivePublicKeysByNetworks( - userWalletId = userWalletId, - networks = currenciesToAdd.values.flatten(), - ) + currenciesRepository.removeCurrencies(userWalletId = userWalletId, currencies = removingCurrencies) - val existingCurrencies = if (tokensFeatureToggles.isWalletBalanceFetcherEnabled) { - multiWalletCryptoCurrenciesSupplier - .getSyncOrNull(params = MultiWalletCryptoCurrenciesProducer.Params(userWalletId)) - .orEmpty() - .toList() - } else { - currenciesRepository.getMultiCurrencyWalletCurrenciesSync(userWalletId) + removeCurrenciesFromWalletManager(userWalletId = userWalletId, currencies = removingCurrencies) } - val newCurrenciesList = existingCurrencies - .filterNot(removingCurrencies::contains) - .toMutableList() - .also { it.addAll(addingCurrencies) } + if (currenciesToAdd.isNotEmpty()) { + derivationsRepository.derivePublicKeysByNetworks( + userWalletId = userWalletId, + networks = currenciesToAdd.values.flatten(), + ) - currenciesRepository.saveNewCurrenciesList(userWalletId, newCurrenciesList) + val addingCurrencies = currenciesToAdd.mapToCryptoCurrencies(userWalletId) - removeCurrenciesFromWalletManager( - userWalletId = userWalletId, - currencies = removingCurrencies.filterNot(existingCurrencies::contains), - ) - refreshUpdatedNetworks( - userWalletId = userWalletId, - existingCurrencies = existingCurrencies, - currenciesToAdd = addingCurrencies, - ) + val savedCurrencies = currenciesRepository.addCurrenciesCache( + userWalletId = userWalletId, + currencies = addingCurrencies, + ) - refreshUpdatedYieldBalances(userWalletId, existingCurrencies) + refreshUpdatedNetworks(userWalletId = userWalletId, addedCurrencies = savedCurrencies) - refreshUpdatedQuotes(addingCurrencies) + refreshUpdatedYieldBalances(userWalletId = userWalletId, addedCurrencies = savedCurrencies) + + refreshUpdatedQuotes(addedCurrencies = savedCurrencies) + } } private suspend fun removeCurrenciesFromWalletManager( @@ -91,36 +76,25 @@ class SaveManagedTokensUseCase( ) } - private suspend fun refreshUpdatedNetworks( - userWalletId: UserWalletId, - currenciesToAdd: List, - existingCurrencies: List, - ) { - val networksToUpdate = currenciesToAdd - .asSequence() - .filterIsInstance() - .map(CryptoCurrency.Token::network) - .filterTo(hashSetOf()) { hasCoinForNetwork(existingCurrencies, it) } - - val networkToUpdate = currenciesToAdd.map { it.network } - .subtract(existingCurrencies.map { it.network }.toSet()) - + private suspend fun refreshUpdatedNetworks(userWalletId: UserWalletId, addedCurrencies: List) { multiNetworkStatusFetcher( MultiNetworkStatusFetcher.Params( userWalletId = userWalletId, - networks = networksToUpdate + networkToUpdate, + networks = addedCurrencies.map(CryptoCurrency::network).toSet(), ), ) + + currenciesRepository.syncTokens(userWalletId) } private suspend fun refreshUpdatedYieldBalances( userWalletId: UserWalletId, - existingCurrencies: List, + addedCurrencies: List, ) { multiYieldBalanceFetcher( params = MultiYieldBalanceFetcher.Params( userWalletId = userWalletId, - currencyIdWithNetworkMap = existingCurrencies.associateTo(hashMapOf()) { it.id to it.network }, + currencyIdWithNetworkMap = addedCurrencies.associateTo(hashMapOf()) { it.id to it.network }, ), ) } @@ -134,16 +108,6 @@ class SaveManagedTokensUseCase( ) } - /** - * Determines if the [existingCurrencies] list contains a coin that corresponds - * to the given [network]. - */ - private fun hasCoinForNetwork(existingCurrencies: List, network: Network): Boolean { - return existingCurrencies.any { currency -> - currency is CryptoCurrency.Coin && currency.network == network - } - } - private suspend fun Map>.mapToCryptoCurrencies( userWalletId: UserWalletId, ): List { 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 250a7f6b63..3decd4fa02 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 @@ -62,7 +62,7 @@ class SaveMarketTokensUseCase( ) } - val savedCurrencies = currenciesRepository.addCurrencies( + val savedCurrencies = currenciesRepository.addCurrenciesCache( userWalletId = userWalletId, currencies = addedCurrencies, ) @@ -82,6 +82,7 @@ class SaveMarketTokensUseCase( networks = addedCurrencies.map(CryptoCurrency::network).toSet(), ), ) + currenciesRepository.syncTokens(userWalletId) } private suspend fun refreshUpdatedYieldBalances( diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/AddCryptoCurrenciesUseCase.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/AddCryptoCurrenciesUseCase.kt index 47c305d0bc..f72f694560 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/AddCryptoCurrenciesUseCase.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/AddCryptoCurrenciesUseCase.kt @@ -129,7 +129,7 @@ class AddCryptoCurrenciesUseCase( * Refreshes the network statuses for tokens that have corresponding coins in the * [existingCurrencies] list. */ - private suspend fun Raise.refreshUpdatedNetworks( + private suspend fun refreshUpdatedNetworks( userWalletId: UserWalletId, currencyToAdd: CryptoCurrency, existingCurrencies: List, @@ -150,7 +150,8 @@ class AddCryptoCurrenciesUseCase( networks = setOfNotNull(networksToUpdate, networkToUpdate), ), ) - .bind() + + currenciesRepository.syncTokens(userWalletId) } private suspend fun refreshUpdatedYieldBalances(userWalletId: UserWalletId, addedCurrency: CryptoCurrency) { @@ -193,7 +194,7 @@ class AddCryptoCurrenciesUseCase( private suspend fun Raise.addCurrencies(userWalletId: UserWalletId, currency: CryptoCurrency) { catch( - { currenciesRepository.addCurrencies(userWalletId, listOf(currency)) }, + { currenciesRepository.addCurrenciesCache(userWalletId, listOf(currency)) }, ) { raise(it) } 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 9225d49a40..d76e51be41 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 @@ -52,6 +52,26 @@ interface CurrenciesRepository { */ suspend fun addCurrencies(userWalletId: UserWalletId, currencies: List): List + /** + * Saves the given list of cryptocurrencies for a specific multi-currency user wallet. + * + * @param userWalletId The unique identifier of the user wallet. + * @param currencies The list of cryptocurrencies to be saved. + */ + @Deprecated("Tech debt") + suspend fun saveNewCurrenciesListCache(userWalletId: UserWalletId, currencies: List) + + /** + * Add currencies to a specific user wallet. + * + * @param userWalletId The unique identifier of the user wallet. + * @param currencies The currencies which must be added. + * @throws DataError.UserWalletError.WrongUserWallet If single-currency user wallet + * ID provided. + */ + @Deprecated("Tech debt") + suspend fun addCurrenciesCache(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 bdff6776cf..dfcb15bef7 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 @@ -53,6 +53,13 @@ internal class MockCurrenciesRepository( currencies: List, ): List = emptyList() + override suspend fun saveNewCurrenciesListCache(userWalletId: UserWalletId, currencies: List) = Unit + + override suspend fun addCurrenciesCache( + userWalletId: UserWalletId, + currencies: List, + ): List = emptyList() + override suspend fun removeCurrency(userWalletId: UserWalletId, currency: CryptoCurrency) { removeCurrencyResult.onLeft { throw it } } diff --git a/domain/wallets/src/main/java/com/tangem/domain/wallets/legacy/UserWalletsListManager.kt b/domain/wallets/src/main/java/com/tangem/domain/wallets/legacy/UserWalletsListManager.kt index bd117a8c8e..c61566c02c 100644 --- a/domain/wallets/src/main/java/com/tangem/domain/wallets/legacy/UserWalletsListManager.kt +++ b/domain/wallets/src/main/java/com/tangem/domain/wallets/legacy/UserWalletsListManager.kt @@ -15,6 +15,9 @@ interface UserWalletsListManager { /** [Flow] with all saved [UserWallet]s updates */ val userWallets: Flow> + /** Count saved wallets updates */ + val savedWalletsCount: Flow + /** [Flow] with selected [UserWallet] updates */ @Deprecated("You should provide the selected wallet via routing parameters due to the scalability of the features") val selectedUserWallet: Flow diff --git a/domain/wallets/src/main/java/com/tangem/domain/wallets/usecase/GetSavedWalletChangesUseCase.kt b/domain/wallets/src/main/java/com/tangem/domain/wallets/usecase/GetSavedWalletsCountUseCase.kt similarity index 55% rename from domain/wallets/src/main/java/com/tangem/domain/wallets/usecase/GetSavedWalletChangesUseCase.kt rename to domain/wallets/src/main/java/com/tangem/domain/wallets/usecase/GetSavedWalletsCountUseCase.kt index afcfe40a89..8bc40b9305 100644 --- a/domain/wallets/src/main/java/com/tangem/domain/wallets/usecase/GetSavedWalletChangesUseCase.kt +++ b/domain/wallets/src/main/java/com/tangem/domain/wallets/usecase/GetSavedWalletsCountUseCase.kt @@ -4,23 +4,21 @@ import com.tangem.domain.wallets.legacy.UserWalletsListManager import com.tangem.domain.wallets.legacy.asLockable import com.tangem.domain.wallets.legacy.isLockedSync import com.tangem.domain.wallets.models.UserWallet -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.distinctUntilChanged -import kotlinx.coroutines.flow.filter +import kotlinx.coroutines.flow.* -class GetSavedWalletChangesUseCase( +class GetSavedWalletsCountUseCase( private val userWalletsListManager: UserWalletsListManager, ) { operator fun invoke(): Flow> { - return userWalletsListManager.userWallets - .filter { + return userWalletsListManager.savedWalletsCount + .filter { count -> + if (count == 0) return@filter true userWalletsListManager.asLockable() ?: return@filter false - return@filter if (userWalletsListManager.isLockedSync.not()) { - it.isNotEmpty() - } else { - false - } + return@filter userWalletsListManager.isLockedSync.not() + } + .map { + userWalletsListManager.userWalletsSync } .distinctUntilChanged() } diff --git a/domain/wallets/src/test/java/com/tangem/domain/wallets/usecase/GetSavedWalletChangesUseCaseTest.kt b/domain/wallets/src/test/java/com/tangem/domain/wallets/usecase/GetSavedWalletChangesUseCaseTest.kt deleted file mode 100644 index 25e0cb17db..0000000000 --- a/domain/wallets/src/test/java/com/tangem/domain/wallets/usecase/GetSavedWalletChangesUseCaseTest.kt +++ /dev/null @@ -1,76 +0,0 @@ -package com.tangem.domain.wallets.usecase - -import com.google.common.truth.Truth.assertThat -import com.tangem.domain.wallets.legacy.UserWalletsListManager -import com.tangem.domain.wallets.legacy.asLockable -import com.tangem.domain.wallets.legacy.isLockedSync -import com.tangem.domain.wallets.models.UserWallet -import io.mockk.every -import io.mockk.mockk -import io.mockk.mockkStatic -import kotlinx.coroutines.flow.firstOrNull -import kotlinx.coroutines.flow.flowOf -import kotlinx.coroutines.test.runTest -import org.junit.Before -import org.junit.Test - -class GetSavedWalletChangesUseCaseTest { - - private lateinit var useCase: GetSavedWalletChangesUseCase - private lateinit var userWalletsListManager: UserWalletsListManager - - @Before - fun setup() { - userWalletsListManager = mockk() - useCase = GetSavedWalletChangesUseCase(userWalletsListManager) - mockkStatic("com.tangem.domain.wallets.legacy.UserWalletsListManagerExtensionsKt") - } - - @Test - fun `GIVEN manager is not lockable WHEN invoke THEN return empty list`() = runTest { - // GIVEN - every { userWalletsListManager.isLockable } returns false - every { userWalletsListManager.userWallets } returns flowOf(emptyList()) - every { userWalletsListManager.isLockedSync } returns false - every { userWalletsListManager.asLockable() } returns null - - // WHEN - val result = useCase().firstOrNull() - - // THEN - assertThat(result).isNull() - } - - @Test - fun `GIVEN manager is locked WHEN invoke THEN return empty list`() = runTest { - // GIVEN - val mockLockable = mockk() - every { userWalletsListManager.isLockable } returns true - every { userWalletsListManager.userWallets } returns flowOf(emptyList()) - every { userWalletsListManager.isLockedSync } returns true - every { userWalletsListManager.asLockable() } returns mockLockable - - // WHEN - val result = useCase().firstOrNull() - - // THEN - assertThat(result).isNull() - } - - @Test - fun `GIVEN manager is not locked and has wallets WHEN invoke THEN return wallets list`() = runTest { - // GIVEN - val mockLockable = mockk() - val wallets = listOf(mockk(), mockk()) - every { userWalletsListManager.isLockable } returns true - every { userWalletsListManager.userWallets } returns flowOf(wallets) - every { userWalletsListManager.isLockedSync } returns false - every { userWalletsListManager.asLockable() } returns mockLockable - - // WHEN - val result = useCase().firstOrNull() - - // THEN - assertThat(result).isEqualTo(wallets) - } -} \ No newline at end of file diff --git a/domain/wallets/src/test/java/com/tangem/domain/wallets/usecase/GetSavedWalletsCountUseCaseTest.kt b/domain/wallets/src/test/java/com/tangem/domain/wallets/usecase/GetSavedWalletsCountUseCaseTest.kt new file mode 100644 index 0000000000..7949741300 --- /dev/null +++ b/domain/wallets/src/test/java/com/tangem/domain/wallets/usecase/GetSavedWalletsCountUseCaseTest.kt @@ -0,0 +1,137 @@ +package com.tangem.domain.wallets.usecase + +import com.google.common.truth.Truth.assertThat +import com.tangem.domain.wallets.legacy.UserWalletsListManager +import com.tangem.domain.wallets.legacy.asLockable +import com.tangem.domain.wallets.legacy.isLockedSync +import com.tangem.domain.wallets.models.UserWallet +import io.mockk.every +import io.mockk.mockk +import io.mockk.mockkStatic +import kotlinx.coroutines.flow.firstOrNull +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Test + +class GetSavedWalletsCountUseCaseTest { + + private lateinit var useCase: GetSavedWalletsCountUseCase + private lateinit var userWalletsListManager: UserWalletsListManager + + @Before + fun setup() { + userWalletsListManager = mockk() + useCase = GetSavedWalletsCountUseCase(userWalletsListManager) + mockkStatic("com.tangem.domain.wallets.legacy.UserWalletsListManagerExtensionsKt") + } + + @Test + fun `GIVEN manager is not lockable WHEN invoke THEN return empty list`() = runTest { + // GIVEN + every { userWalletsListManager.isLockable } returns false + every { userWalletsListManager.savedWalletsCount } returns flowOf(0) + every { userWalletsListManager.userWallets } returns flowOf(emptyList()) + every { userWalletsListManager.userWalletsSync } returns emptyList() + every { userWalletsListManager.isLockedSync } returns false + every { userWalletsListManager.asLockable() } returns null + + // WHEN + val result = useCase().firstOrNull() + + // THEN + assertThat(result).isEmpty() + } + + @Test + fun `GIVEN manager is locked WHEN invoke THEN return empty list`() = runTest { + // GIVEN + val mockLockable = mockk() + every { userWalletsListManager.isLockable } returns true + every { userWalletsListManager.savedWalletsCount } returns flowOf(0) + every { userWalletsListManager.userWallets } returns flowOf(emptyList()) + every { userWalletsListManager.userWalletsSync } returns emptyList() + every { userWalletsListManager.isLockedSync } returns true + every { userWalletsListManager.asLockable() } returns mockLockable + + // WHEN + val result = useCase().firstOrNull() + + // THEN + assertThat(result).isEmpty() + } + + @Test + fun `GIVEN manager is not locked and has wallets WHEN invoke THEN return wallets list`() = runTest { + // GIVEN + val mockLockable = mockk() + val wallets = listOf(mockk(), mockk()) + every { userWalletsListManager.isLockable } returns true + every { userWalletsListManager.savedWalletsCount } returns flowOf(wallets.size) + every { userWalletsListManager.userWallets } returns flowOf(wallets) + every { userWalletsListManager.userWalletsSync } returns wallets + every { userWalletsListManager.isLockedSync } returns false + every { userWalletsListManager.asLockable() } returns mockLockable + + // WHEN + val result = useCase().firstOrNull() + + // THEN + assertThat(result).isEqualTo(wallets) + } + + @Test + fun `GIVEN manager is not lockable and savedWalletsCount is zero WHEN invoke THEN return empty list`() = runTest { + // GIVEN + every { userWalletsListManager.isLockable } returns false + every { userWalletsListManager.savedWalletsCount } returns flowOf(0) + every { userWalletsListManager.userWallets } returns flowOf(emptyList()) + every { userWalletsListManager.userWalletsSync } returns emptyList() + every { userWalletsListManager.isLockedSync } returns false + every { userWalletsListManager.asLockable() } returns null + + // WHEN + val result = useCase().firstOrNull() + + // THEN + assertThat(result).isEmpty() + } + + @Test + fun `GIVEN manager is lockable and locked and savedWalletsCount is zero WHEN invoke THEN return empty list`() = + runTest { + // GIVEN + val mockLockable = mockk() + every { userWalletsListManager.isLockable } returns true + every { userWalletsListManager.savedWalletsCount } returns flowOf(0) + every { userWalletsListManager.userWallets } returns flowOf(emptyList()) + every { userWalletsListManager.userWalletsSync } returns emptyList() + every { userWalletsListManager.isLockedSync } returns true + every { userWalletsListManager.asLockable() } returns mockLockable + + // WHEN + val result = useCase().firstOrNull() + + // THEN + assertThat(result).isEmpty() + } + + @Test + fun `GIVEN manager is lockable and unlocked and savedWalletsCount is zero WHEN invoke THEN return empty list`() = + runTest { + // GIVEN + val mockLockable = mockk() + every { userWalletsListManager.isLockable } returns true + every { userWalletsListManager.savedWalletsCount } returns flowOf(0) + every { userWalletsListManager.userWallets } returns flowOf(emptyList()) + every { userWalletsListManager.userWalletsSync } returns emptyList() + every { userWalletsListManager.isLockedSync } returns false + every { userWalletsListManager.asLockable() } returns mockLockable + + // WHEN + val result = useCase().firstOrNull() + + // THEN + assertThat(result).isEmpty() + } +} \ No newline at end of file diff --git a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/amount/model/SendAmountModel.kt b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/amount/model/SendAmountModel.kt index 97a7e61118..9a90094164 100644 --- a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/amount/model/SendAmountModel.kt +++ b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/amount/model/SendAmountModel.kt @@ -82,6 +82,10 @@ internal class SendAmountModel @Inject constructor( configAmountNavigation() initAppCurrency() subscribeOnCryptoCurrencyStatusFlow() + subscribeOnAmountReduceByTriggerUpdates() + subscribeOnAmountReduceToTriggerUpdates() + subscribeOnAmountIgnoreReduceTriggerUpdates() + subscribeOnAmountUpdateTriggerUpdates() } private fun initAppCurrency() { @@ -104,12 +108,6 @@ internal class SendAmountModel @Inject constructor( .onEach { newCryptoCurrencyStatus -> cryptoCurrencyStatus = newCryptoCurrencyStatus maxAmountBoundary = MaxEnterAmountConverter().convert(cryptoCurrencyStatus) - if (uiState.value is AmountState.Empty) { - subscribeOnAmountReduceByTriggerUpdates() - subscribeOnAmountReduceToTriggerUpdates() - subscribeOnAmountIgnoreReduceTriggerUpdates() - subscribeOnAmountUpdateTriggerUpdates() - } initMinBoundary() } .launchIn(modelScope) diff --git a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/fee/ui/SendCustomFee.kt b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/fee/ui/SendCustomFee.kt index 2486f3bbd9..577020369e 100644 --- a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/fee/ui/SendCustomFee.kt +++ b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/fee/ui/SendCustomFee.kt @@ -29,6 +29,7 @@ internal fun SendCustomFee( hasNotifications: Boolean, onValueChange: (Int, String) -> Unit, onNonceChange: (String) -> Unit, + displayNonceInput: Boolean, nonce: String?, modifier: Modifier = Modifier, ) { @@ -85,10 +86,12 @@ internal fun SendCustomFee( } } } - Nonce( - onNonceChange = onNonceChange, - nonce = nonce, - ) + if (displayNonceInput) { + Nonce( + onNonceChange = onNonceChange, + nonce = nonce, + ) + } } } } diff --git a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/fee/ui/SendFeeContent.kt b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/fee/ui/SendFeeContent.kt index c017356770..ff60febdfa 100644 --- a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/fee/ui/SendFeeContent.kt +++ b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/fee/ui/SendFeeContent.kt @@ -41,6 +41,7 @@ internal fun SendFeeContent(state: FeeUM, clickIntents: SendFeeClickIntents) { feeSelectorUM = feeSelectorUM, onValueChange = clickIntents::onCustomFeeValueChange, onNonceChange = clickIntents::onNonceChange, + displayNonceInput = state.displayNonceInput, nonce = state.nonce?.toString().orEmpty(), hasNotifications = hasNotifications, ) @@ -59,11 +60,13 @@ private fun LazyListScope.feeSelector(state: FeeUM.Content, clickIntents: SendFe } } +@Suppress("LongParameterList") internal fun LazyListScope.customFee( feeSelectorUM: FeeSelectorUM.Content, hasNotifications: Boolean, onValueChange: (Int, String) -> Unit, onNonceChange: (String) -> Unit, + displayNonceInput: Boolean, nonce: String?, modifier: Modifier = Modifier, ) { @@ -74,6 +77,7 @@ internal fun LazyListScope.customFee( hasNotifications = hasNotifications, onValueChange = onValueChange, onNonceChange = onNonceChange, + displayNonceInput = displayNonceInput, nonce = nonce, modifier = modifier .fillMaxWidth() diff --git a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/notifications/model/NotificationsModel.kt b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/notifications/model/NotificationsModel.kt index 510533e56d..a895060169 100644 --- a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/notifications/model/NotificationsModel.kt +++ b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/notifications/model/NotificationsModel.kt @@ -304,7 +304,7 @@ internal class NotificationsModel @Inject constructor( ) { val validationError = validateTransactionUseCase( userWalletId = userWalletId, - amount = enteredAmount.convertToSdkAmount(currency), + amount = sendingAmount.convertToSdkAmount(currency), fee = fee, memo = memo, destination = destinationAddress, diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/StakingInitialInfoContent.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/StakingInitialInfoContent.kt index e984f97d3d..1cccaf852d 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/StakingInitialInfoContent.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/StakingInitialInfoContent.kt @@ -240,6 +240,7 @@ private fun StakingRewardBlock( title = resourceReference(R.string.staking_rewards), text = text, iconRes = R.drawable.ic_chevron_right_24.takeIf { isShowIcon }, + onIconClick = onRewardsClick, textColor = textColor, modifier = Modifier .clip(TangemTheme.shapes.roundedCornersXMedium) diff --git a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt index 61a4be30c1..8cf53366e1 100644 --- a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt +++ b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt @@ -21,6 +21,7 @@ import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.domain.models.network.Network import com.tangem.domain.models.quote.QuoteStatus import com.tangem.domain.quotes.QuotesRepository +import com.tangem.domain.quotes.multi.MultiQuoteStatusFetcher import com.tangem.domain.tokens.* import com.tangem.domain.tokens.model.CryptoCurrencyStatus import com.tangem.domain.tokens.model.FeePaidCurrency @@ -64,6 +65,7 @@ internal class SwapInteractorImpl @AssistedInject constructor( private val createApprovalTransactionUseCase: CreateApprovalTransactionUseCase, private val isDemoCardUseCase: IsDemoCardUseCase, private val quotesRepository: QuotesRepository, + private val multiQuoteStatusFetcher: MultiQuoteStatusFetcher, private val swapTransactionRepository: SwapTransactionRepository, private val currencyChecksRepository: CurrencyChecksRepository, private val appCurrencyRepository: AppCurrencyRepository, @@ -1279,7 +1281,7 @@ internal class SwapInteractorImpl @AssistedInject constructor( network = network, userWallet = userWallet, ).getOrNull() ?: error("unable to calculate fee") - } catch (e: IllegalStateException) { + } catch (_: IllegalStateException) { getEthSpecificFeeUseCase( userWallet = userWallet, cryptoCurrency = fromToken, @@ -1805,20 +1807,41 @@ internal class SwapInteractorImpl @AssistedInject constructor( } private suspend fun getQuotes(vararg ids: CryptoCurrency.ID): Map { - val set = ids.mapNotNull { it.rawCurrencyId } - .toSet() + val set = ids.mapNotNullTo(destination = hashSetOf(), transform = CryptoCurrency.ID::rawCurrencyId) .getQuotesOrEmpty() return ids .mapNotNull { id -> - set.find { it.rawCurrencyId == id.rawCurrencyId && it.value is QuoteStatus.Data } - ?.let { id to (it.value as QuoteStatus.Data).fiatRate } + val found = set.find { it.rawCurrencyId == id.rawCurrencyId && it.value is QuoteStatus.Data } + ?: return@mapNotNull null + + id to (found.value as QuoteStatus.Data).fiatRate } .toMap() } private suspend fun Set.getQuotesOrEmpty(): Set { - return runCatching { quotesRepository.getMultiQuoteSyncOrNull(currenciesIds = this) } + return runCatching { + val cachedQuotes = quotesRepository.getMultiQuoteSyncOrNull(currenciesIds = this) + + val allQuotesFound = cachedQuotes?.all { it.value !is QuoteStatus.Empty } == true + + if (allQuotesFound) return@runCatching cachedQuotes + + val currenciesIds = if (cachedQuotes.isNullOrEmpty()) { + this + } else { + cachedQuotes.mapNotNullTo(hashSetOf()) { + if (it.value is QuoteStatus.Empty) it.rawCurrencyId else null + } + } + + multiQuoteStatusFetcher( + params = MultiQuoteStatusFetcher.Params(currenciesIds = currenciesIds, appCurrencyId = null), + ) + + quotesRepository.getMultiQuoteSyncOrNull(currenciesIds = this) + } .getOrNull() .orEmpty() } diff --git a/features/swap/impl/src/main/java/com/tangem/feature/swap/ui/SwapSelectTokenScreen.kt b/features/swap/impl/src/main/java/com/tangem/feature/swap/ui/SwapSelectTokenScreen.kt index 03ebfc66ca..f2821d83a4 100644 --- a/features/swap/impl/src/main/java/com/tangem/feature/swap/ui/SwapSelectTokenScreen.kt +++ b/features/swap/impl/src/main/java/com/tangem/feature/swap/ui/SwapSelectTokenScreen.kt @@ -16,10 +16,12 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.ColorFilter import androidx.compose.ui.res.painterResource import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.Preview import com.tangem.core.ui.components.SpacerH12 import com.tangem.core.ui.components.SpacerW2 import com.tangem.core.ui.components.appbar.ExpandableSearchView +import com.tangem.core.ui.components.atoms.text.EllipsisText import com.tangem.core.ui.components.currency.icon.CurrencyIcon import com.tangem.core.ui.components.currency.icon.CurrencyIconState import com.tangem.core.ui.decorations.roundedShapeItemDecoration @@ -222,10 +224,11 @@ private fun TokenItem( Column( modifier = Modifier + .weight(1f) .align(Alignment.CenterVertically) .padding(start = TangemTheme.dimens.spacing12), ) { - Text( + EllipsisText( text = token.name, style = TangemTheme.typography.subtitle1, color = if (token.available) { @@ -235,20 +238,19 @@ private fun TokenItem( }, ) SpacerW2() - Text( + EllipsisText( text = token.symbol, style = TangemTheme.typography.caption2, color = TangemTheme.colors.text.tertiary, ) } - Spacer(modifier = Modifier.weight(1f)) - if (token.addedTokenBalanceData != null) { Column( horizontalAlignment = Alignment.End, verticalArrangement = Arrangement.Center, - modifier = Modifier.padding(start = TangemTheme.dimens.spacing8), + modifier = Modifier + .padding(start = TangemTheme.dimens.spacing8), ) { Text( text = token.addedTokenBalanceData.amountEquivalent.orEmpty().orMaskWithStars( @@ -256,6 +258,9 @@ private fun TokenItem( !token.addedTokenBalanceData.amountEquivalent.isNullOrEmpty(), ), style = TangemTheme.typography.subtitle1, + maxLines = 1, + softWrap = false, + overflow = TextOverflow.Visible, color = if (token.available) { TangemTheme.colors.text.primary1 } else { @@ -268,6 +273,7 @@ private fun TokenItem( maskWithStars = token.addedTokenBalanceData.isBalanceHidden && !token.addedTokenBalanceData.amount.isNullOrEmpty(), ), + maxLines = 1, style = TangemTheme.typography.caption2, color = TangemTheme.colors.text.tertiary, ) @@ -284,12 +290,11 @@ private val token = TokenToSelectState.TokenToSelect( showCustomBadge = false, ), id = "", - name = "USDC", + name = "Optimistic Ethereum (ETH)", symbol = "USDC", addedTokenBalanceData = TokenBalanceData( amount = "15 000 $", - amountEquivalent = "15 000 " + - "USDT", + amountEquivalent = "15 000 USDT", isBalanceHidden = false, ), ) 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 2d629ddca7..b6d2f0e7dd 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,7 +26,6 @@ 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 @@ -46,7 +45,6 @@ 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 @@ -74,8 +72,6 @@ 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() @@ -196,20 +192,10 @@ 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/child/wallet/model/intents/WalletWarningsClickIntents.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/wallet/model/intents/WalletWarningsClickIntents.kt index cbc0e161ef..fec895abb5 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/wallet/model/intents/WalletWarningsClickIntents.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/wallet/model/intents/WalletWarningsClickIntents.kt @@ -18,12 +18,15 @@ import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.domain.networks.multi.MultiNetworkStatusFetcher import com.tangem.domain.promo.ShouldShowPromoWalletUseCase import com.tangem.domain.promo.models.PromoId +import com.tangem.domain.quotes.multi.MultiQuoteStatusFetcher import com.tangem.domain.settings.NeverToSuggestRateAppUseCase import com.tangem.domain.settings.RemindToRateAppLaterUseCase +import com.tangem.domain.staking.multi.MultiYieldBalanceFetcher import com.tangem.domain.tokens.model.analytics.TokenSwapPromoAnalyticsEvent import com.tangem.domain.wallets.legacy.UserWalletsListManager.Lockable.UnlockType import com.tangem.domain.wallets.models.UnlockWalletsError import com.tangem.domain.wallets.models.UserWallet +import com.tangem.domain.wallets.models.UserWalletId import com.tangem.domain.wallets.models.requireColdWallet import com.tangem.domain.wallets.usecase.GetUserWalletUseCase import com.tangem.domain.wallets.usecase.SeedPhraseNotificationUseCase @@ -40,6 +43,9 @@ import com.tangem.feature.wallet.presentation.wallet.state.model.WalletEvent import com.tangem.feature.wallet.presentation.wallet.state.transformers.CloseBottomSheetTransformer import com.tangem.feature.wallet.presentation.wallet.state.utils.WalletEventSender import com.tangem.utils.coroutines.CoroutineDispatcherProvider +import kotlinx.coroutines.async +import kotlinx.coroutines.awaitAll +import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.launch import timber.log.Timber import javax.inject.Inject @@ -105,6 +111,8 @@ internal class WalletWarningsClickIntentsImplementor @Inject constructor( private val seedPhraseNotificationUseCase: SeedPhraseNotificationUseCase, private val urlOpener: UrlOpener, private val multiNetworkStatusFetcher: MultiNetworkStatusFetcher, + private val multiQuoteStatusFetcher: MultiQuoteStatusFetcher, + private val multiYieldBalanceFetcher: MultiYieldBalanceFetcher, private val appRouter: AppRouter, ) : BaseWalletClickIntents(), WalletWarningsClickIntents { @@ -148,13 +156,7 @@ internal class WalletWarningsClickIntentsImplementor @Inject constructor( ).fold( ifLeft = { Timber.e(it, "Failed to derive public keys") }, ifRight = { - multiNetworkStatusFetcher( - params = MultiNetworkStatusFetcher.Params( - userWalletId = userWallet.walletId, - networks = missedAddressCurrencies.map(CryptoCurrency::network).toSet(), - ), - ) - .onLeft { Timber.e("Unable to refresh token list: $it") } + fetchCryptoCurrencies(userWalletId = userWallet.walletId, currencies = missedAddressCurrencies) }, ) } @@ -375,6 +377,41 @@ internal class WalletWarningsClickIntentsImplementor @Inject constructor( // TODO implement wallet activation process } + private suspend fun fetchCryptoCurrencies(userWalletId: UserWalletId, currencies: List) { + coroutineScope { + listOf( + async { + multiNetworkStatusFetcher( + params = MultiNetworkStatusFetcher.Params( + userWalletId = userWalletId, + networks = currencies.map(CryptoCurrency::network).toSet(), + ), + ) + .onLeft { Timber.e("Unable to fetch networks: $it") } + }, + async { + multiQuoteStatusFetcher( + params = MultiQuoteStatusFetcher.Params( + currenciesIds = currencies.mapNotNull { it.id.rawCurrencyId }.toSet(), + appCurrencyId = null, + ), + ) + .onLeft { Timber.e("Unable to fetch quotes: $it") } + }, + async { + multiYieldBalanceFetcher( + params = MultiYieldBalanceFetcher.Params( + userWalletId = userWalletId, + currencyIdWithNetworkMap = currencies.associate { it.id to it.network }, + ), + ) + .onLeft { Timber.e("Unable to fetch yield balances: $it") } + }, + ) + .awaitAll() + } + } + private fun getSelectedUserWallet(): UserWallet? { val userWalletId = stateHolder.getSelectedWalletId() return getUserWalletUseCase(userWalletId).getOrElse { diff --git a/gradle/tangem_dependencies.toml b/gradle/tangem_dependencies.toml index e81d85f69c..80b280a86a 100644 --- a/gradle/tangem_dependencies.toml +++ b/gradle/tangem_dependencies.toml @@ -5,9 +5,9 @@ # https://github.com/tangem/tangem-sdk-android/ # https://github.com/tangem/vico -tangemBlockchainSdk = "develop-1119" +tangemBlockchainSdk = "develop-1121" #tangemBlockchainSdk = "0.0.1" # Keep it! - used for local builds -tangemCardSdk = "develop-489" +tangemCardSdk = "develop-501" #tangemCardSdk = "0.0.1" # Keep it! - used for local builds ^ tangemVico = "2.0.0-alpha.25-tangem12" #tangemVico = "0.0.1" # Keep it! - used for local builds ^ diff --git a/libs/tangem-sdk-api/src/main/kotlin/com/tangem/sdk/api/di/CardSdkModule.kt b/libs/tangem-sdk-api/src/main/kotlin/com/tangem/sdk/api/di/CardSdkModule.kt index 513b751434..936f54845e 100644 --- a/libs/tangem-sdk-api/src/main/kotlin/com/tangem/sdk/api/di/CardSdkModule.kt +++ b/libs/tangem-sdk-api/src/main/kotlin/com/tangem/sdk/api/di/CardSdkModule.kt @@ -1,11 +1,14 @@ package com.tangem.sdk.api.di +import android.content.Context import com.tangem.domain.card.repository.CardSdkConfigRepository import com.tangem.operations.attestation.CardArtworksProvider import dagger.Module import dagger.Provides import dagger.hilt.InstallIn +import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.components.SingletonComponent +import java.io.File import javax.inject.Singleton @Module @@ -14,10 +17,16 @@ internal object CardSdkModule { @Provides @Singleton - fun provideCardArtworksProvider(sdkRepository: CardSdkConfigRepository): CardArtworksProvider { + fun provideCardArtworksProvider( + sdkRepository: CardSdkConfigRepository, + @ApplicationContext context: Context, + ): CardArtworksProvider { return CardArtworksProvider( tangemApiBaseUrlProvider = { sdkRepository.sdk.config.tangemApiBaseUrl }, - secureStorage = sdkRepository.sdk.secureStorage, + artworksDirectory = File( + context.getExternalFilesDir(null) ?: context.filesDir, + "card_artworks", + ).also { it.mkdirs() }, ) } } \ No newline at end of file