Updated on 2026-08-14
This commit is contained in:
commit
5a83aace89
19 changed files with 152 additions and 131 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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>,
|
||||
) : 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<String>): Map<String, SecretKey> {
|
||||
return keystoreManagerProvider().get(keyAliases)
|
||||
override suspend fun get(
|
||||
masterKeyConfig: KeystoreManager.MasterKeyConfig,
|
||||
keyAliases: Collection<String>,
|
||||
): Map<String, SecretKey> {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -96,5 +96,6 @@ internal sealed interface TokensListStateHolder {
|
|||
override val tokens: Flow<PagingData<TokenItemState>>,
|
||||
override val onTokensLoadStateChanged: (LoadState) -> Unit,
|
||||
val onSaveButtonClick: () -> Unit,
|
||||
val isSavingInProgress: Boolean,
|
||||
) : TokensListStateHolder
|
||||
}
|
||||
|
|
@ -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<Toke
|
|||
),
|
||||
onSaveButtonClick = {},
|
||||
onTokensLoadStateChanged = {},
|
||||
isSavingInProgress = false,
|
||||
),
|
||||
TokensListStateHolder.ReadContent(
|
||||
toolbarState = TokensListToolbarState.Title.Read(
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ package com.tangem.tap.features.tokens.impl.presentation.viewmodels
|
|||
import arrow.core.Either
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.blockchain.common.Token
|
||||
import com.tangem.core.navigation.AppScreen
|
||||
import com.tangem.core.navigation.NavigationAction
|
||||
import com.tangem.data.tokens.utils.CryptoCurrencyFactory
|
||||
import com.tangem.domain.card.DerivePublicKeysUseCase
|
||||
|
|
@ -15,7 +16,7 @@ import com.tangem.domain.wallets.models.UserWallet
|
|||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.domain.wallets.usecase.GetSelectedWalletSyncUseCase
|
||||
import com.tangem.tap.common.extensions.dispatchDebugErrorNotification
|
||||
import com.tangem.tap.common.extensions.dispatchOnMain
|
||||
import com.tangem.tap.common.extensions.dispatchWithMain
|
||||
import com.tangem.tap.common.extensions.inject
|
||||
import com.tangem.tap.proxy.redux.DaggerGraphState
|
||||
import com.tangem.tap.store
|
||||
|
|
@ -125,7 +126,7 @@ internal class TokensListMigration(
|
|||
val isNothingToDoWithBlockchain = blockchainsToAdd.isEmpty() && blockchainsToRemove.isEmpty()
|
||||
if (isNothingToDoWithTokens && isNothingToDoWithBlockchain) {
|
||||
store.dispatchDebugErrorNotification(message = "Nothing to save")
|
||||
store.dispatchOnMain(NavigationAction.PopBackTo())
|
||||
store.dispatchWithMain(NavigationAction.PopBackTo(screen = AppScreen.Wallet))
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -134,9 +135,8 @@ internal class TokensListMigration(
|
|||
derivePublicKeysUseCase(userWalletId = currentUserWallet.walletId, currencies = currencyList)
|
||||
.onRight {
|
||||
addCryptoCurrenciesUseCase(userWalletId = currentUserWallet.walletId, currencies = currencyList)
|
||||
store.dispatchOnMain(NavigationAction.PopBackTo())
|
||||
}
|
||||
.onLeft { Timber.e("Failed to derive public keys: $it") }
|
||||
.onLeft { Timber.e(it, "Failed to derive public keys") }
|
||||
}
|
||||
|
||||
private suspend fun removeCurrenciesIfNeeded(userWalletId: UserWalletId, currencies: List<CryptoCurrency>) {
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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<Value : Any>(
|
|||
private val adapter: JsonAdapter<Value>,
|
||||
) : StringKeyDataStore<Value> {
|
||||
|
||||
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<Value : Any>(
|
|||
|
||||
override fun get(key: String): Flow<Value> {
|
||||
return writeTrigger
|
||||
.map { getInternal(key) }
|
||||
.map {
|
||||
mutex.withLock {
|
||||
getInternal(key)
|
||||
}
|
||||
}
|
||||
.filterNotNull()
|
||||
.distinctUntilChanged()
|
||||
}
|
||||
|
|
@ -53,10 +60,12 @@ internal class FileDataStore<Value : Any>(
|
|||
|
||||
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")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@
|
|||
},
|
||||
{
|
||||
"name": "GENERAL_USER_WALLETS_LIST_MANAGER_ENABLED",
|
||||
"version": "5.7.0"
|
||||
"version": "5.8.0"
|
||||
},
|
||||
{
|
||||
"name": "LOCAL_USER_LOGS_ENABLED",
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
|
|
|
|||
|
|
@ -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<String, Mutex>()
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -56,8 +56,9 @@ internal class DefaultNetworksRepository(
|
|||
.cancellable()
|
||||
|
||||
override suspend fun fetchNetworkPendingTransactions(userWalletId: UserWalletId, networks: Set<Network>) {
|
||||
val currencies = getCurrencies(userWalletId, networks)
|
||||
withContext(dispatchers.io) {
|
||||
fetchNetworksPendingTransactions(userWalletId, networks)
|
||||
fetchNetworksPendingTransactions(userWalletId, networks, currencies)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -80,23 +81,28 @@ internal class DefaultNetworksRepository(
|
|||
networks: Set<Network>,
|
||||
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<Network>) {
|
||||
private suspend fun fetchNetworksPendingTransactions(
|
||||
userWalletId: UserWalletId,
|
||||
networks: Set<Network>,
|
||||
currencies: Sequence<CryptoCurrency>,
|
||||
) {
|
||||
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<CryptoCurrency>,
|
||||
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<CryptoCurrency>,
|
||||
) {
|
||||
val result = walletManagersFacade.update(
|
||||
userWalletId = userWalletId,
|
||||
network = network,
|
||||
extraTokens = currencies.filterIsInstance<CryptoCurrency.Token>().toSet(),
|
||||
extraTokens = currencies
|
||||
.filterIsInstance<CryptoCurrency.Token>()
|
||||
.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<CryptoCurrency>,
|
||||
) {
|
||||
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<CryptoCurrency> {
|
||||
private suspend fun getCurrencies(userWalletId: UserWalletId, networks: Set<Network>): Sequence<CryptoCurrency> {
|
||||
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(
|
||||
|
|
|
|||
|
|
@ -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<CryptoCurrency.ID>): Flow<Set<Quote>> {
|
||||
|
|
@ -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<String>, appCurrencyId: String) {
|
||||
|
|
@ -118,7 +124,6 @@ internal class DefaultQuotesRepository(
|
|||
): Set<String> {
|
||||
return currenciesIds.fold(hashSetOf()) { acc, currencyId ->
|
||||
val rawCurrencyId = currencyId.rawCurrencyId
|
||||
|
||||
if (rawCurrencyId != null && rawCurrencyId !in acc) {
|
||||
cacheRegistry.invokeOnExpire(
|
||||
key = getQuoteCacheKey(rawCurrencyId),
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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<UserWallet>, currentState: WalletScreenState, canSaveWallets: Boolean): Action {
|
||||
fun resolve(wallets: List<UserWallet>, 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<UserWallet>,
|
||||
selectedWallet: UserWallet,
|
||||
canSaveWallets: Boolean,
|
||||
): Action {
|
||||
this.isInitialized = true
|
||||
this.canSaveWallets = canSaveWallets
|
||||
|
||||
private fun createInitializeWalletsAction(wallets: List<UserWallet>, 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<UserWallet>,
|
||||
|
|
@ -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
|
||||
*
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue