diff --git a/app/src/main/java/com/tangem/tap/domain/TangemSdkManager.kt b/app/src/main/java/com/tangem/tap/domain/TangemSdkManager.kt index d9e620497a..94b72fcf1e 100644 --- a/app/src/main/java/com/tangem/tap/domain/TangemSdkManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/TangemSdkManager.kt @@ -6,13 +6,14 @@ import androidx.annotation.StringRes import com.tangem.Message import com.tangem.TangemSdk import com.tangem.common.* -import com.tangem.common.authentication.KeystoreManager +import com.tangem.common.authentication.keystore.KeystoreManager import com.tangem.common.card.FirmwareVersion import com.tangem.common.core.* import com.tangem.common.extensions.ByteArrayKey import com.tangem.common.services.secure.SecureStorage import com.tangem.common.usersCode.UserCodeRepository import com.tangem.core.analytics.Analytics +import com.tangem.core.analytics.models.Basic import com.tangem.crypto.bip39.DefaultMnemonic import com.tangem.crypto.hdWallet.DerivationPath import com.tangem.domain.card.repository.CardSdkConfigRepository @@ -25,7 +26,6 @@ import com.tangem.operations.derivation.DerivationTaskResponse import com.tangem.operations.derivation.DeriveMultipleWalletPublicKeysTask import com.tangem.operations.pins.SetUserCodeCommand import com.tangem.operations.usersetttings.SetUserCodeRecoveryAllowedTask -import com.tangem.core.analytics.models.Basic import com.tangem.tap.derivationsFinder import com.tangem.tap.domain.tasks.product.CreateProductWalletTask import com.tangem.tap.domain.tasks.product.CreateProductWalletTaskResponse diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/di/UserWalletsListManagerModule.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/di/UserWalletsListManagerModule.kt index 1a162862e6..f2e03181e1 100644 --- a/app/src/main/java/com/tangem/tap/domain/userWalletList/di/UserWalletsListManagerModule.kt +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/di/UserWalletsListManagerModule.kt @@ -3,7 +3,7 @@ package com.tangem.tap.domain.userWalletList.di import android.content.Context import com.squareup.moshi.Moshi import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory -import com.tangem.common.authentication.AuthenticatedStorage +import com.tangem.common.authentication.storage.AuthenticatedStorage import com.tangem.common.json.TangemSdkAdapter import com.tangem.common.services.secure.SecureStorage import com.tangem.datasource.local.preferences.AppPreferencesStore diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/di/UserWalletsListManagerProvider.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/di/UserWalletsListManagerProvider.kt index efb44252c2..51bdd56ae6 100644 --- a/app/src/main/java/com/tangem/tap/domain/userWalletList/di/UserWalletsListManagerProvider.kt +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/di/UserWalletsListManagerProvider.kt @@ -3,7 +3,7 @@ package com.tangem.tap.domain.userWalletList.di import android.content.Context import com.squareup.moshi.Moshi import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory -import com.tangem.common.authentication.AuthenticatedStorage +import com.tangem.common.authentication.storage.AuthenticatedStorage import com.tangem.common.json.TangemSdkAdapter import com.tangem.common.services.secure.SecureStorage import com.tangem.domain.wallets.legacy.UserWalletsListManager 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 cf7497ea51..6fccee5d59 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 @@ -131,16 +131,25 @@ internal class GeneralUserWalletsListManager( Timber.d("Switch to ${manager::class.java.simpleName}") implementation.value = manager + + clearOldManager(manager) } .flowOn(dispatchers.io) .launchIn(applicationScope) } - /** Copy data from [old] manager and clean it */ + /** Copy data from [old] manager */ private suspend fun UserWalletsListManager.copyFrom(old: UserWalletsListManager): UserWalletsListManager { old.selectedUserWalletSync?.let { this.save(it) } - old.clear() return this } + + private suspend fun clearOldManager(current: UserWalletsListManager) { + if (current == biometricUserWalletsListManager) { + runtimeUserWalletsListManager.clear() + } else { + biometricUserWalletsListManager.clear() + } + } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/DelegatedKeystoreManager.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/DelegatedKeystoreManager.kt index 3c548bd255..f61d6df0d0 100644 --- a/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/DelegatedKeystoreManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/DelegatedKeystoreManager.kt @@ -1,6 +1,6 @@ package com.tangem.tap.domain.userWalletList.repository -import com.tangem.common.authentication.KeystoreManager +import com.tangem.common.authentication.keystore.KeystoreManager import com.tangem.utils.Provider import javax.crypto.SecretKey @@ -8,15 +8,18 @@ internal class DelegatedKeystoreManager( private val keystoreManagerProvider: Provider, ) : KeystoreManager { - override suspend fun get(keyAlias: String): SecretKey? { - return keystoreManagerProvider().get(keyAlias) + override suspend fun get(masterKeyConfig: KeystoreManager.MasterKeyConfig, keyAlias: String): SecretKey? { + return keystoreManagerProvider().get(masterKeyConfig, keyAlias) } - override suspend fun get(keyAliases: Collection): Map { - return keystoreManagerProvider().get(keyAliases) + override suspend fun get( + masterKeyConfig: KeystoreManager.MasterKeyConfig, + keyAliases: Collection, + ): Map { + return keystoreManagerProvider().get(masterKeyConfig, keyAliases) } - override suspend fun store(keyAlias: String, key: SecretKey) { - return keystoreManagerProvider().store(keyAlias, key) + override suspend fun store(masterKeyConfig: KeystoreManager.MasterKeyConfig, keyAlias: String, key: SecretKey) { + keystoreManagerProvider().store(masterKeyConfig, keyAlias, key) } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/implementation/BiometricUserWalletsKeysRepository.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/implementation/BiometricUserWalletsKeysRepository.kt index 0b06c9565f..d04e0122b7 100644 --- a/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/implementation/BiometricUserWalletsKeysRepository.kt +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/implementation/BiometricUserWalletsKeysRepository.kt @@ -4,7 +4,7 @@ import com.squareup.moshi.JsonAdapter import com.squareup.moshi.Moshi import com.squareup.moshi.Types import com.tangem.common.* -import com.tangem.common.authentication.AuthenticatedStorage +import com.tangem.common.authentication.storage.AuthenticatedStorage import com.tangem.common.core.TangemSdkError import com.tangem.common.services.secure.SecureStorage import com.tangem.domain.wallets.legacy.UserWalletsListError diff --git a/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/states/TokensListStateHolder.kt b/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/states/TokensListStateHolder.kt index 101cdc8f52..bf48a93269 100644 --- a/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/states/TokensListStateHolder.kt +++ b/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/states/TokensListStateHolder.kt @@ -96,5 +96,6 @@ internal sealed interface TokensListStateHolder { override val tokens: Flow>, override val onTokensLoadStateChanged: (LoadState) -> Unit, val onSaveButtonClick: () -> Unit, + val isSavingInProgress: Boolean, ) : TokensListStateHolder } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/ui/TokensListScreen.kt b/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/ui/TokensListScreen.kt index 3dff613b36..70335b9609 100644 --- a/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/ui/TokensListScreen.kt +++ b/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/ui/TokensListScreen.kt @@ -29,7 +29,10 @@ import androidx.compose.ui.unit.TextUnit import androidx.compose.ui.unit.TextUnitType import androidx.compose.ui.unit.dp import androidx.paging.PagingData -import androidx.paging.compose.* +import androidx.paging.compose.LazyPagingItems +import androidx.paging.compose.collectAsLazyPagingItems +import androidx.paging.compose.itemContentType +import androidx.paging.compose.itemKey import com.tangem.core.ui.components.PrimaryButton import com.tangem.core.ui.res.TangemTheme import com.tangem.tap.features.tokens.impl.presentation.states.TokenItemState @@ -63,10 +66,11 @@ internal fun TokensListScreen(stateHolder: TokensListStateHolder, modifier: Modi val verticalPadding = TangemTheme.dimens.spacing32 SaveChangesButton( - onClick = stateHolder.onSaveButtonClick, modifier = Modifier.onSizeChanged { with(density) { floatingButtonHeight = it.height.toDp() + verticalPadding } }, + showProgress = stateHolder.isSavingInProgress, + onClick = stateHolder.onSaveButtonClick, ) } }, @@ -176,13 +180,14 @@ private fun DifferentAddressesWarning() { } @Composable -private fun SaveChangesButton(onClick: () -> Unit, modifier: Modifier = Modifier) { +private fun SaveChangesButton(showProgress: Boolean, onClick: () -> Unit, modifier: Modifier = Modifier) { PrimaryButton( modifier = modifier .imePadding() .padding(horizontal = TangemTheme.dimens.spacing16) .fillMaxWidth(), text = stringResource(id = R.string.common_save_changes), + showProgress = showProgress, onClick = onClick, ) } @@ -237,6 +242,7 @@ private class TokensListScreenProvider : CollectionPreviewParameterProvider) { diff --git a/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/viewmodels/TokensListViewModel.kt b/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/viewmodels/TokensListViewModel.kt index 0853b72b8f..2464c82a17 100644 --- a/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/viewmodels/TokensListViewModel.kt +++ b/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/viewmodels/TokensListViewModel.kt @@ -11,6 +11,8 @@ import androidx.lifecycle.viewModelScope import androidx.paging.* import com.tangem.blockchain.common.Blockchain import com.tangem.core.analytics.api.AnalyticsEventHandler +import com.tangem.core.navigation.AppScreen +import com.tangem.core.navigation.NavigationAction import com.tangem.core.ui.extensions.getActiveIconRes import com.tangem.core.ui.extensions.getGreyedOutIconRes import com.tangem.domain.card.DerivePublicKeysUseCase @@ -24,6 +26,7 @@ import com.tangem.domain.tokens.AddCryptoCurrenciesUseCase import com.tangem.domain.tokens.GetCryptoCurrenciesUseCase import com.tangem.domain.tokens.TokenWithBlockchain import com.tangem.domain.wallets.usecase.GetSelectedWalletSyncUseCase +import com.tangem.tap.common.extensions.dispatchWithMain import com.tangem.tap.common.extensions.fullNameWithoutTestnet import com.tangem.tap.common.extensions.getNetworkName import com.tangem.tap.features.tokens.impl.domain.TokensListInteractor @@ -122,6 +125,7 @@ internal class TokensListViewModel @Inject constructor( isDifferentAddressesBlockVisible = isDifferentAddressesBlockVisible(), tokens = getInitialTokensList(), onTokensLoadStateChanged = actionsHandler::onTokensLoadStateChanged, + isSavingInProgress = false, onSaveButtonClick = actionsHandler::onSaveButtonClick, ) } else { @@ -308,13 +312,20 @@ internal class TokensListViewModel @Inject constructor( } fun onSaveButtonClick() { + val state = uiState as? TokensListStateHolder.ManageContent ?: return + analyticsSender.sendWhenSaveButtonClicked() viewModelScope.launch(dispatchers.main) { + uiState = state.copy(isSavingInProgress = true) + tokensListMigration.onSaveButtonClick( changedTokensList = changedTokensList, changedBlockchainList = changedBlockchainList, ) + + uiState = state.copy(isSavingInProgress = false) + store.dispatchWithMain(NavigationAction.PopBackTo(screen = AppScreen.Wallet)) } } diff --git a/core/datasource/src/main/java/com/tangem/datasource/local/datastore/FileDataStore.kt b/core/datasource/src/main/java/com/tangem/datasource/local/datastore/FileDataStore.kt index e9b2317f35..f67f25584f 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/local/datastore/FileDataStore.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/local/datastore/FileDataStore.kt @@ -8,6 +8,8 @@ import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.flow.map +import kotlinx.coroutines.sync.Mutex +import kotlinx.coroutines.sync.withLock import timber.log.Timber @Deprecated("Use shared preferences data store instead") @@ -16,6 +18,7 @@ internal class FileDataStore( private val adapter: JsonAdapter, ) : StringKeyDataStore { + private val mutex = Mutex() private val writeTrigger = Trigger() override suspend fun isEmpty(): Boolean { val e = NotImplementedError("`isEmpty()` function not implemented for `FileDataStore`") @@ -28,7 +31,11 @@ internal class FileDataStore( override fun get(key: String): Flow { return writeTrigger - .map { getInternal(key) } + .map { + mutex.withLock { + getInternal(key) + } + } .filterNotNull() .distinctUntilChanged() } @@ -53,10 +60,12 @@ internal class FileDataStore( override suspend fun store(key: String, value: Value) { try { - val json = adapter.toJson(value) + mutex.withLock { + val json = adapter.toJson(value) - fileReader.rewriteFile(json, key) - writeTrigger.trigger() + fileReader.rewriteFile(json, key) + writeTrigger.trigger() + } } catch (e: Throwable) { Timber.e(e, "Unable to write file: $key") } diff --git a/core/featuretoggles/src/main/assets/configs/feature_toggles_config.json b/core/featuretoggles/src/main/assets/configs/feature_toggles_config.json index 1b7b643cea..639da533da 100644 --- a/core/featuretoggles/src/main/assets/configs/feature_toggles_config.json +++ b/core/featuretoggles/src/main/assets/configs/feature_toggles_config.json @@ -17,7 +17,7 @@ }, { "name": "GENERAL_USER_WALLETS_LIST_MANAGER_ENABLED", - "version": "5.7.0" + "version": "5.8.0" }, { "name": "LOCAL_USER_LOGS_ENABLED", diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/buttons/actions/Actions.kt b/core/ui/src/main/java/com/tangem/core/ui/components/buttons/actions/Actions.kt index 9b56a22f57..3d0b87e26a 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/buttons/actions/Actions.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/buttons/actions/Actions.kt @@ -11,6 +11,7 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.painterResource import androidx.compose.ui.text.style.TextOverflow @@ -86,7 +87,8 @@ private fun Button( Row( modifier = modifier .heightIn(min = TangemTheme.dimens.size36) - .background(color = backgroundColor, shape = shape) + .clip(shape) + .background(color = backgroundColor) .clickable(enabled = config.enabled, onClick = config.onClick) .padding(start = TangemTheme.dimens.spacing16, end = TangemTheme.dimens.spacing24) .padding(vertical = TangemTheme.dimens.spacing8), diff --git a/data/common/src/main/kotlin/com/tangem/data/common/cache/DefaultCacheRegistry.kt b/data/common/src/main/kotlin/com/tangem/data/common/cache/DefaultCacheRegistry.kt index 6d6f19cd0c..d0938c6718 100644 --- a/data/common/src/main/kotlin/com/tangem/data/common/cache/DefaultCacheRegistry.kt +++ b/data/common/src/main/kotlin/com/tangem/data/common/cache/DefaultCacheRegistry.kt @@ -3,15 +3,21 @@ package com.tangem.data.common.cache import com.tangem.datasource.local.cache.CacheKeysStore import com.tangem.datasource.local.cache.model.CacheKey import kotlinx.coroutines.NonCancellable +import kotlinx.coroutines.sync.Mutex +import kotlinx.coroutines.sync.withLock import kotlinx.coroutines.withContext import org.joda.time.Duration import org.joda.time.LocalDateTime import timber.log.Timber +import java.util.concurrent.ConcurrentHashMap internal class DefaultCacheRegistry( private val cacheKeysStore: CacheKeysStore, ) : CacheRegistry { + private val mutex = Mutex() + private val mutexes = ConcurrentHashMap() + override suspend fun isExpired(key: String): Boolean { val cacheKey = cacheKeysStore.getSyncOrNull(key) ?: return true @@ -41,27 +47,36 @@ internal class DefaultCacheRegistry( expireIn: Duration, block: suspend () -> Unit, ) { - val isExpired = isExpired(key) || skipCache - if (!isExpired) return + // use a separate mutexForKey for each key to avoid multiple calls block() to the same key + // also used mutex to safe create mutexForKey, otherwise it can lead to multiple calls for the same key + val mutexForKey = mutex.withLock { + mutexes.getOrPut(key) { Mutex() } + } + mutexForKey.withLock { + val isExpired = isExpired(key) || skipCache + if (!isExpired) { + return + } - try { - Timber.d("Invoke the action associated with the cache key: $key") + try { + Timber.d("Invoke the action associated with the cache key: $key") - cacheKeysStore.store( - key = CacheKey( - id = key, - updatedAt = LocalDateTime.now(), - expiresIn = expireIn, - ), - ) + cacheKeysStore.store( + key = CacheKey( + id = key, + updatedAt = LocalDateTime.now(), + expiresIn = expireIn, + ), + ) - block() - } catch (e: Throwable) { - Timber.e(e, "The action related to the cache key has failed: $key") + block() + } catch (e: Throwable) { + Timber.e(e, "The action related to the cache key has failed: $key") - invalidate(key) + invalidate(key) - throw e + throw e + } } } } \ No newline at end of file diff --git a/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultNetworksRepository.kt b/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultNetworksRepository.kt index fc4296fdb8..44096bdcd7 100644 --- a/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultNetworksRepository.kt +++ b/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultNetworksRepository.kt @@ -56,8 +56,9 @@ internal class DefaultNetworksRepository( .cancellable() override suspend fun fetchNetworkPendingTransactions(userWalletId: UserWalletId, networks: Set) { + val currencies = getCurrencies(userWalletId, networks) withContext(dispatchers.io) { - fetchNetworksPendingTransactions(userWalletId, networks) + fetchNetworksPendingTransactions(userWalletId, networks, currencies) } } @@ -80,23 +81,28 @@ internal class DefaultNetworksRepository( networks: Set, refresh: Boolean, ) { + val currencies = getCurrencies(userWalletId, networks) coroutineScope { networks .map { network -> async { - fetchNetworkStatusIfCacheExpired(userWalletId, network, refresh) + fetchNetworkStatusIfCacheExpired(userWalletId, network, currencies, refresh) } } .awaitAll() } } - private suspend fun fetchNetworksPendingTransactions(userWalletId: UserWalletId, networks: Set) { + private suspend fun fetchNetworksPendingTransactions( + userWalletId: UserWalletId, + networks: Set, + currencies: Sequence, + ) { coroutineScope { networks .map { network -> async { - fetchNetworkPendingTransactions(userWalletId, network) + fetchNetworkPendingTransactions(userWalletId, network, currencies) } } .awaitAll() @@ -106,22 +112,28 @@ internal class DefaultNetworksRepository( private suspend fun fetchNetworkStatusIfCacheExpired( userWalletId: UserWalletId, network: Network, + currencies: Sequence, refresh: Boolean, ) { cacheRegistry.invokeOnExpire( key = getNetworksStatusesCacheKey(userWalletId, network), skipCache = refresh, - block = { fetchNetworkStatus(userWalletId, network) }, + block = { fetchNetworkStatus(userWalletId, network, currencies) }, ) } - private suspend fun fetchNetworkStatus(userWalletId: UserWalletId, network: Network) { - val currencies = getCurrencies(userWalletId, network) - + private suspend fun fetchNetworkStatus( + userWalletId: UserWalletId, + network: Network, + currencies: Sequence, + ) { val result = walletManagersFacade.update( userWalletId = userWalletId, network = network, - extraTokens = currencies.filterIsInstance().toSet(), + extraTokens = currencies + .filterIsInstance() + .filter { it.network == network } + .toSet(), ) withContext(NonCancellable) { @@ -137,9 +149,11 @@ internal class DefaultNetworksRepository( networksStatusesStore.store(userWalletId, networkStatus) } - private suspend fun fetchNetworkPendingTransactions(userWalletId: UserWalletId, network: Network) { - val currencies = getCurrencies(userWalletId, network) - + private suspend fun fetchNetworkPendingTransactions( + userWalletId: UserWalletId, + network: Network, + currencies: Sequence, + ) { val result = walletManagersFacade.updatePendingTransactions( userWalletId = userWalletId, network = network, @@ -158,7 +172,7 @@ internal class DefaultNetworksRepository( networksStatusesStore.store(userWalletId, networkStatus) } - private suspend fun getCurrencies(userWalletId: UserWalletId, network: Network): Sequence { + private suspend fun getCurrencies(userWalletId: UserWalletId, networks: Set): Sequence { val userWallet = requireNotNull(userWalletsStore.getSyncOrNull(userWalletId)) { "Unable to find user wallet with provided ID: $userWalletId" } @@ -180,7 +194,7 @@ internal class DefaultNetworksRepository( } } - return currencies.filter { it.network == network } + return currencies.filter { networks.contains(it.network) } } private suspend fun invalidateCacheKeyIfNeeded( diff --git a/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultQuotesRepository.kt b/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultQuotesRepository.kt index bd834c5450..d362189832 100644 --- a/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultQuotesRepository.kt +++ b/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultQuotesRepository.kt @@ -17,6 +17,8 @@ import com.tangem.domain.tokens.repository.QuotesRepository import com.tangem.utils.coroutines.CoroutineDispatcherProvider import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.* +import kotlinx.coroutines.sync.Mutex +import kotlinx.coroutines.sync.withLock import kotlinx.coroutines.withContext internal class DefaultQuotesRepository( @@ -32,6 +34,7 @@ internal class DefaultQuotesRepository( @Volatile private var quotesFetchedForAppCurrency: String? = null + private val mutex = Mutex() @OptIn(ExperimentalCoroutinesApi::class) override fun getQuotesUpdates(currenciesIds: Set): Flow> { @@ -42,7 +45,6 @@ internal class DefaultQuotesRepository( .filterNotNull() .flatMapLatest { appCurrency -> fetchExpiredQuotes(currenciesIds, appCurrency.id, refresh = false) - quotesStore.get(currenciesIds).map(quotesConverter::convertSet) } .cancellable() @@ -79,15 +81,19 @@ internal class DefaultQuotesRepository( appCurrencyId: String, refresh: Boolean, ) { - val expiredCurrenciesIds = filterExpiredCurrenciesIds( - currenciesIds = currenciesIds, - refresh = refresh || quotesFetchedForAppCurrency != appCurrencyId, - ) - if (expiredCurrenciesIds.isEmpty()) return + // TODO("[REDACTED_JIRA]") need refactor working with quotesFetchedForAppCurrency, + // it changes after filterExpiredCurrenciesIds + // calls with different coroutines and lead to fetchQuotes + mutex.withLock { + val expiredCurrenciesIds = filterExpiredCurrenciesIds( + currenciesIds = currenciesIds, + refresh = refresh || quotesFetchedForAppCurrency != appCurrencyId, + ) + if (expiredCurrenciesIds.isEmpty()) return - quotesFetchedForAppCurrency = appCurrencyId - - fetchQuotes(expiredCurrenciesIds, appCurrencyId) + quotesFetchedForAppCurrency = appCurrencyId + fetchQuotes(expiredCurrenciesIds, appCurrencyId) + } } private suspend fun fetchQuotes(rawCurrenciesIds: Set, appCurrencyId: String) { @@ -118,7 +124,6 @@ internal class DefaultQuotesRepository( ): Set { return currenciesIds.fold(hashSetOf()) { acc, currencyId -> val rawCurrencyId = currencyId.rawCurrencyId - if (rawCurrencyId != null && rawCurrencyId !in acc) { cacheRegistry.invokeOnExpire( key = getQuoteCacheKey(rawCurrencyId), diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletViewModel.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletViewModel.kt index 1581b36984..467f87f0fa 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletViewModel.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletViewModel.kt @@ -14,7 +14,6 @@ import com.tangem.domain.settings.ShouldShowSaveWalletScreenUseCase import com.tangem.domain.walletconnect.WalletConnectActions import com.tangem.domain.wallets.usecase.GetSelectedWalletUseCase import com.tangem.domain.wallets.usecase.GetWalletsUseCase -import com.tangem.domain.wallets.usecase.ShouldSaveUserWalletsUseCase import com.tangem.feature.wallet.presentation.deeplink.WalletDeepLinksHandler import com.tangem.feature.wallet.presentation.router.InnerWalletRouter import com.tangem.feature.wallet.presentation.wallet.analytics.WalletScreenAnalyticsEvent @@ -53,7 +52,6 @@ internal class WalletViewModel @Inject constructor( private val getWalletsUseCase: GetWalletsUseCase, private val shouldShowSaveWalletScreenUseCase: ShouldShowSaveWalletScreenUseCase, private val canUseBiometryUseCase: CanUseBiometryUseCase, - private val shouldSaveUserWalletsUseCase: ShouldSaveUserWalletsUseCase, private val isWalletsScrollPreviewEnabled: IsWalletsScrollPreviewEnabled, private val getBalanceHidingSettingsUseCase: GetBalanceHidingSettingsUseCase, analyticsEventsHandler: AnalyticsEventHandler, @@ -74,7 +72,7 @@ internal class WalletViewModel @Inject constructor( suggestToEnableBiometrics() - subscribeOnWalletsUpdateFlow() + subscribeToUserWalletsUpdates() subscribeOnBalanceHiding() subscribeOnSelectedWalletFlow() } @@ -112,25 +110,12 @@ internal class WalletViewModel @Inject constructor( return router.isWalletLastScreen() && shouldShowSaveWalletScreenUseCase() && canUseBiometryUseCase() } - private fun subscribeOnWalletsUpdateFlow() { - viewModelScope.launch(dispatchers.main) { - shouldSaveUserWalletsUseCase() - .conflate() - .distinctUntilChanged() - .collectLatest(::subscribeToUserWalletsUpdates) - } - } - - private fun subscribeToUserWalletsUpdates(shouldSaveUserWallet: Boolean) { + private fun subscribeToUserWalletsUpdates() { getWalletsUseCase() .conflate() .distinctUntilChanged() .map { - walletsUpdateActionResolver.resolve( - wallets = it, - currentState = stateHolder.value, - canSaveWallets = shouldSaveUserWallet, - ) + walletsUpdateActionResolver.resolve(wallets = it, currentState = stateHolder.value) } .onEach(::updateWallets) .flowOn(dispatchers.main) @@ -182,14 +167,6 @@ internal class WalletViewModel @Inject constructor( private suspend fun updateWallets(action: WalletsUpdateActionResolver.Action) { when (action) { is WalletsUpdateActionResolver.Action.InitializeWallets -> initializeWallets(action) - is WalletsUpdateActionResolver.Action.ReinitializeWallets -> { - walletScreenContentLoader.load( - userWallet = action.selectedWallet, - clickIntents = clickIntents, - isRefresh = true, - coroutineScope = viewModelScope, - ) - } is WalletsUpdateActionResolver.Action.ReinitializeWallet -> reinitializeWallet(action) is WalletsUpdateActionResolver.Action.AddWallet -> addWallet(action) is WalletsUpdateActionResolver.Action.DeleteWallet -> deleteWallet(action) diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletsUpdateActionResolver.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletsUpdateActionResolver.kt index 5e221221de..012aee200d 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletsUpdateActionResolver.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletsUpdateActionResolver.kt @@ -22,24 +22,16 @@ internal class WalletsUpdateActionResolver @Inject constructor( private val getSelectedWalletSyncUseCase: GetSelectedWalletSyncUseCase, ) { - private var isInitialized: Boolean = false - private var canSaveWallets: Boolean = false - - fun resolve(wallets: List, currentState: WalletScreenState, canSaveWallets: Boolean): Action { + fun resolve(wallets: List, currentState: WalletScreenState): Action { val selectedWallet = wallets.getSelectedWallet() val action = if (selectedWallet == null) { createNoSelectedWalletAction(wallets) } else { - when { - isFirstInitialization(currentState) -> { - createInitializeWalletsAction(wallets, selectedWallet, canSaveWallets) - } - isReinitialization(canSaveWallets) -> { - this.canSaveWallets = canSaveWallets - Action.ReinitializeWallets(selectedWallet = selectedWallet) - } - else -> getUpdateContentAction(currentState, wallets, selectedWallet) + if (isFirstInitialization(currentState)) { + createInitializeWalletsAction(wallets, selectedWallet) + } else { + getUpdateContentAction(currentState, wallets, selectedWallet) } } @@ -68,14 +60,7 @@ internal class WalletsUpdateActionResolver @Inject constructor( return state.selectedWalletIndex == NOT_INITIALIZED_WALLET_INDEX } - private fun createInitializeWalletsAction( - wallets: List, - selectedWallet: UserWallet, - canSaveWallets: Boolean, - ): Action { - this.isInitialized = true - this.canSaveWallets = canSaveWallets - + private fun createInitializeWalletsAction(wallets: List, selectedWallet: UserWallet): Action { return Action.InitializeWallets( selectedWalletIndex = wallets.indexOfWallet(selectedWallet.walletId), selectedWallet = selectedWallet, @@ -83,10 +68,6 @@ internal class WalletsUpdateActionResolver @Inject constructor( ) } - private fun isReinitialization(canSaveWallets: Boolean): Boolean { - return isInitialized && this.canSaveWallets != canSaveWallets - } - private fun getUpdateContentAction( state: WalletScreenState, wallets: List, @@ -232,18 +213,6 @@ internal class WalletsUpdateActionResolver @Inject constructor( } } - /** - * Reinitialize wallets. Example, if user turned on wallets saving - * - * @property selectedWallet selected wallet - */ - data class ReinitializeWallets(val selectedWallet: UserWallet) : Action() { - - override fun toString(): String { - return "ReinitializeWallets(selectedWallet = ${selectedWallet.walletId})" - } - } - /** * Reinitialize selected wallet. Example, scanning a new card if wallets saving is turned off * diff --git a/gradle/dependencies.toml b/gradle/dependencies.toml index 0065774dfd..46c976be0f 100644 --- a/gradle/dependencies.toml +++ b/gradle/dependencies.toml @@ -87,7 +87,7 @@ web3j = "4.10.1" # region Tangem tangemBlockchainSdk = "release-app_5.8-525" #tangemBlockchainSdk = "0.0.1" # Keep it! - used for local builds -tangemCardSdk = "release-app_5.8-333" +tangemCardSdk = "release-app_5.8-335" #tangemCardSdk = "0.0.1" # Keep it! - used for local builds ^ # endregion Tangem