Updated on 2026-08-14
This commit is contained in:
commit
86d0d58706
31 changed files with 446 additions and 235 deletions
18
LICENSE
Normal file
18
LICENSE
Normal file
|
|
@ -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.
|
||||
|
|
@ -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,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -214,8 +214,8 @@ internal object WalletsDomainModule {
|
|||
@Singleton
|
||||
fun providesGetSavedWalletChangesIdUseCase(
|
||||
userWalletsListManager: UserWalletsListManager,
|
||||
): GetSavedWalletChangesUseCase {
|
||||
return GetSavedWalletChangesUseCase(
|
||||
): GetSavedWalletsCountUseCase {
|
||||
return GetSavedWalletsCountUseCase(
|
||||
userWalletsListManager = userWalletsListManager,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -36,6 +36,11 @@ internal class BiometricUserWalletsListManager(
|
|||
.mapLatest { it.userWallets }
|
||||
.distinctUntilChanged()
|
||||
|
||||
override val savedWalletsCount: Flow<Int>
|
||||
get() = state
|
||||
.mapLatest { walletsCount }
|
||||
.distinctUntilChanged()
|
||||
|
||||
override val userWalletsSync: List<UserWallet>
|
||||
get() = state.value.userWallets
|
||||
|
||||
|
|
|
|||
|
|
@ -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<Int>
|
||||
get() = implementation
|
||||
.transformLatest { impl ->
|
||||
if (impl != null) {
|
||||
emitAll(impl.savedWalletsCount)
|
||||
}
|
||||
}
|
||||
|
||||
override val userWalletsSync: List<UserWallet>
|
||||
get() = requireImplementation.userWalletsSync
|
||||
|
||||
|
|
|
|||
|
|
@ -20,6 +20,11 @@ internal class RuntimeUserWalletsListManager : UserWalletsListManager {
|
|||
.mapLatest { listOfNotNull(it.userWallet) }
|
||||
.distinctUntilChanged()
|
||||
|
||||
override val savedWalletsCount: Flow<Int>
|
||||
get() = state
|
||||
.mapLatest { walletsCount }
|
||||
.distinctUntilChanged()
|
||||
|
||||
override val selectedUserWallet: Flow<UserWallet>
|
||||
get() = state
|
||||
.mapLatest { it.userWallet }
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -129,6 +129,61 @@ internal class DefaultCurrenciesRepository(
|
|||
currenciesToAdd
|
||||
}
|
||||
|
||||
override suspend fun saveNewCurrenciesListCache(userWalletId: UserWalletId, currencies: List<CryptoCurrency>) {
|
||||
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<CryptoCurrency>,
|
||||
): List<CryptoCurrency> = 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<UserTokensResponse.Token>,
|
||||
currenciesToAdd: List<CryptoCurrency>,
|
||||
|
|
@ -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? {
|
||||
|
|
|
|||
|
|
@ -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<ManagedCryptoCurrency.Token, Set<Network>>,
|
||||
currenciesToRemove: Map<ManagedCryptoCurrency.Token, Set<Network>>,
|
||||
): Either<Throwable, Unit> = 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<CryptoCurrency>,
|
||||
existingCurrencies: List<CryptoCurrency>,
|
||||
) {
|
||||
val networksToUpdate = currenciesToAdd
|
||||
.asSequence()
|
||||
.filterIsInstance<CryptoCurrency.Token>()
|
||||
.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<CryptoCurrency>) {
|
||||
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<CryptoCurrency>,
|
||||
addedCurrencies: List<CryptoCurrency>,
|
||||
) {
|
||||
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<CryptoCurrency>, network: Network): Boolean {
|
||||
return existingCurrencies.any { currency ->
|
||||
currency is CryptoCurrency.Coin && currency.network == network
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun Map<ManagedCryptoCurrency.Token, Set<Network>>.mapToCryptoCurrencies(
|
||||
userWalletId: UserWalletId,
|
||||
): List<CryptoCurrency> {
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -129,7 +129,7 @@ class AddCryptoCurrenciesUseCase(
|
|||
* Refreshes the network statuses for tokens that have corresponding coins in the
|
||||
* [existingCurrencies] list.
|
||||
*/
|
||||
private suspend fun Raise<Throwable>.refreshUpdatedNetworks(
|
||||
private suspend fun refreshUpdatedNetworks(
|
||||
userWalletId: UserWalletId,
|
||||
currencyToAdd: CryptoCurrency,
|
||||
existingCurrencies: List<CryptoCurrency>,
|
||||
|
|
@ -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<Throwable>.addCurrencies(userWalletId: UserWalletId, currency: CryptoCurrency) {
|
||||
catch(
|
||||
{ currenciesRepository.addCurrencies(userWalletId, listOf(currency)) },
|
||||
{ currenciesRepository.addCurrenciesCache(userWalletId, listOf(currency)) },
|
||||
) {
|
||||
raise(it)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -52,6 +52,26 @@ interface CurrenciesRepository {
|
|||
*/
|
||||
suspend fun addCurrencies(userWalletId: UserWalletId, currencies: List<CryptoCurrency>): List<CryptoCurrency>
|
||||
|
||||
/**
|
||||
* 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<CryptoCurrency>)
|
||||
|
||||
/**
|
||||
* 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<CryptoCurrency>): List<CryptoCurrency>
|
||||
|
||||
/**
|
||||
* Removes currency from a specific user wallet.
|
||||
*
|
||||
|
|
|
|||
|
|
@ -53,6 +53,13 @@ internal class MockCurrenciesRepository(
|
|||
currencies: List<CryptoCurrency>,
|
||||
): List<CryptoCurrency> = emptyList()
|
||||
|
||||
override suspend fun saveNewCurrenciesListCache(userWalletId: UserWalletId, currencies: List<CryptoCurrency>) = Unit
|
||||
|
||||
override suspend fun addCurrenciesCache(
|
||||
userWalletId: UserWalletId,
|
||||
currencies: List<CryptoCurrency>,
|
||||
): List<CryptoCurrency> = emptyList()
|
||||
|
||||
override suspend fun removeCurrency(userWalletId: UserWalletId, currency: CryptoCurrency) {
|
||||
removeCurrencyResult.onLeft { throw it }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,6 +15,9 @@ interface UserWalletsListManager {
|
|||
/** [Flow] with all saved [UserWallet]s updates */
|
||||
val userWallets: Flow<List<UserWallet>>
|
||||
|
||||
/** Count saved wallets updates */
|
||||
val savedWalletsCount: Flow<Int>
|
||||
|
||||
/** [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<UserWallet>
|
||||
|
|
|
|||
|
|
@ -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<List<UserWallet>> {
|
||||
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()
|
||||
}
|
||||
|
|
@ -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<UserWalletsListManager.Lockable>()
|
||||
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<UserWalletsListManager.Lockable>()
|
||||
val wallets = listOf(mockk<UserWallet>(), mockk<UserWallet>())
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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<UserWalletsListManager.Lockable>()
|
||||
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<UserWalletsListManager.Lockable>()
|
||||
val wallets = listOf(mockk<UserWallet>(), mockk<UserWallet>())
|
||||
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<UserWalletsListManager.Lockable>()
|
||||
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<UserWalletsListManager.Lockable>()
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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<CryptoCurrency.ID, BigDecimal> {
|
||||
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<CryptoCurrency.RawID>.getQuotesOrEmpty(): Set<QuoteStatus> {
|
||||
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()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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<CryptoCurrency>) {
|
||||
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 {
|
||||
|
|
|
|||
|
|
@ -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 ^
|
||||
|
|
|
|||
|
|
@ -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() },
|
||||
)
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue