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 e0b08af719..e9bc518930 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 @@ -112,6 +112,7 @@ internal object UserWalletsListManagerModule { publicInformationRepository = publicInformationRepository, sensitiveInformationRepository = sensitiveInformationRepository, selectedUserWalletRepository = selectedUserWalletRepository, + dispatcherProvider = dispatchers, ) } diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/BiometricUserWalletsListManager.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/BiometricUserWalletsListManager.kt index 6e9ca21efb..8183415675 100644 --- a/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/BiometricUserWalletsListManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/BiometricUserWalletsListManager.kt @@ -16,11 +16,13 @@ import com.tangem.tap.domain.userWalletList.utils.encryptionKey import com.tangem.tap.domain.userWalletList.utils.lockAll import com.tangem.tap.domain.userWalletList.utils.toUserWallets import com.tangem.tap.domain.userWalletList.utils.updateWith +import com.tangem.utils.coroutines.CoroutineDispatcherProvider import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.* import kotlinx.coroutines.runBlocking import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock +import kotlinx.coroutines.withContext import timber.log.Timber @Suppress("LargeClass") @@ -30,6 +32,7 @@ internal class BiometricUserWalletsListManager( private val publicInformationRepository: UserWalletsPublicInformationRepository, private val sensitiveInformationRepository: UserWalletsSensitiveInformationRepository, private val selectedUserWalletRepository: SelectedUserWalletRepository, + private val dispatcherProvider: CoroutineDispatcherProvider, ) : UserWalletsListManager.Lockable { private val state = MutableStateFlow(State()) @@ -72,12 +75,14 @@ internal class BiometricUserWalletsListManager( return runBlocking { // workaround to avoid calling hasSavedEncryptionKeys many times because of performance savedWalletMutex.withLock { + Timber.i("Checking if user has saved wallets") val hasSavedWalletsLocal = hasSavedWallets if (hasSavedWalletsLocal == null || !hasSavedWalletsLocal) { val hasKeys = keysRepository.hasSavedEncryptionKeys() hasSavedWallets = hasKeys hasKeys } else { + Timber.i("User has saved wallets (from cache)") true } } @@ -93,23 +98,25 @@ internal class BiometricUserWalletsListManager( .distinctUntilChanged() override suspend fun unlock(type: UnlockType): CompletionResult { - return unlockAndSetSelectedUserWallet(type) - .mapFailure { error -> - Timber.e(error, "Unable to unlock user wallets") - if (error is UserWalletsListError) { - error - } else { - UserWalletsListError.UnableToUnlockUserWallets(error) + return withContext(dispatcherProvider.io) { + unlockAndSetSelectedUserWallet(type) + .mapFailure { error -> + Timber.e(error, "Unable to unlock user wallets") + if (error is UserWalletsListError) { + error + } else { + UserWalletsListError.UnableToUnlockUserWallets(error) + } } - } - .map { selectedUserWallet -> - if (selectedUserWallet == null || selectedUserWallet.isLocked) { - Timber.e("Unable to find selected user wallet") - throw UserWalletsListError.NoUserWalletSelected - } else { - selectedUserWallet + .map { selectedUserWallet -> + if (selectedUserWallet == null || selectedUserWallet.isLocked) { + Timber.e("Unable to find selected user wallet") + throw UserWalletsListError.NoUserWalletSelected + } else { + selectedUserWallet + } } - } + } } override fun lock() { @@ -141,18 +148,20 @@ internal class BiometricUserWalletsListManager( } override suspend fun save(userWallet: UserWallet, canOverride: Boolean): CompletionResult { - return if (canOverride) { - saveInternal(userWallet, changeSelectedUserWallet = true, canOverridePublicInfo = false) - } else { - val isWalletSaved = state.value.userWallets - .any { - it.walletId == userWallet.walletId - } - - if (isWalletSaved) { - CompletionResult.Failure(UserWalletsListError.WalletAlreadySaved) - } else { + return withContext(dispatcherProvider.io) { + if (canOverride) { saveInternal(userWallet, changeSelectedUserWallet = true, canOverridePublicInfo = false) + } else { + val isWalletSaved = state.value.userWallets + .any { + it.walletId == userWallet.walletId + } + + if (isWalletSaved) { + CompletionResult.Failure(UserWalletsListError.WalletAlreadySaved) + } else { + saveInternal(userWallet, changeSelectedUserWallet = true, canOverridePublicInfo = false) + } } } } @@ -161,16 +170,18 @@ internal class BiometricUserWalletsListManager( userWalletId: UserWalletId, update: suspend (UserWallet) -> UserWallet, ): CompletionResult { - return get(userWalletId) - .map { storedUserWallet -> - update(storedUserWallet) - } - .flatMap { updatedUserWallet -> - saveInternal(updatedUserWallet, changeSelectedUserWallet = false, canOverridePublicInfo = true) - } - .flatMap { - get(userWalletId) - } + return withContext(dispatcherProvider.io) { + get(userWalletId) + .map { storedUserWallet -> + update(storedUserWallet) + } + .flatMap { updatedUserWallet -> + saveInternal(updatedUserWallet, changeSelectedUserWallet = false, canOverridePublicInfo = true) + } + .flatMap { + get(userWalletId) + } + } } override suspend fun delete(userWalletIds: List): CompletionResult { @@ -187,44 +198,48 @@ internal class BiometricUserWalletsListManager( return clear() } - return sensitiveInformationRepository.delete(idsToRemove) - .flatMap { publicInformationRepository.delete(idsToRemove) } - .map { keysRepository.delete(idsToRemove) } - .map { - state.update { prevState -> - val remainingWallets = prevState.userWallets.filter { it.walletId !in idsToRemove } + return withContext(dispatcherProvider.io) { + sensitiveInformationRepository.delete(idsToRemove) + .flatMap { publicInformationRepository.delete(idsToRemove) } + .map { keysRepository.delete(idsToRemove) } + .map { + state.update { prevState -> + val remainingWallets = prevState.userWallets.filter { it.walletId !in idsToRemove } - val isSelectedWalletDeleted = prevState.selectedUserWalletId in idsToRemove - val newSelectedUserWallet = findOrSetSelectedWallet( - prevSelectedWalletId = prevState.selectedUserWalletId, - prevSelectedWalletIndex = prevState.userWallets.indexOfFirst { - it.walletId == prevState.selectedUserWalletId - }, - userWallets = remainingWallets, - ignorePrevSelectedWallet = isSelectedWalletDeleted, - ) + val isSelectedWalletDeleted = prevState.selectedUserWalletId in idsToRemove + val newSelectedUserWallet = findOrSetSelectedWallet( + prevSelectedWalletId = prevState.selectedUserWalletId, + prevSelectedWalletIndex = prevState.userWallets.indexOfFirst { + it.walletId == prevState.selectedUserWalletId + }, + userWallets = remainingWallets, + ignorePrevSelectedWallet = isSelectedWalletDeleted, + ) - prevState.copy( - encryptionKeys = prevState.encryptionKeys.filter { it.walletId !in idsToRemove }, - userWallets = remainingWallets, - isLocked = remainingWallets.any { it.isLocked }, - selectedUserWalletId = newSelectedUserWallet?.walletId, - ) + prevState.copy( + encryptionKeys = prevState.encryptionKeys.filter { it.walletId !in idsToRemove }, + userWallets = remainingWallets, + isLocked = remainingWallets.any { it.isLocked }, + selectedUserWalletId = newSelectedUserWallet?.walletId, + ) + } } - } + } } override suspend fun clear(): CompletionResult { savedWalletMutex.withLock { hasSavedWallets = null } - return sensitiveInformationRepository.clear() - .flatMap { publicInformationRepository.clear() } - .map { - keysRepository.clear() - selectedUserWalletRepository.set(null) - state.value = State() - } + return withContext(dispatcherProvider.io) { + sensitiveInformationRepository.clear() + .flatMap { publicInformationRepository.clear() } + .map { + keysRepository.clear() + selectedUserWalletRepository.set(null) + state.value = State() + } + } } override suspend fun get(userWalletId: UserWalletId): CompletionResult { diff --git a/app/src/test/kotlin/com/tangem/tap/domain/userWalletList/implementation/BiometricUserWalletsListManagerTest.kt b/app/src/test/kotlin/com/tangem/tap/domain/userWalletList/implementation/BiometricUserWalletsListManagerTest.kt index 3d7fa216d8..7e54ffc6bd 100644 --- a/app/src/test/kotlin/com/tangem/tap/domain/userWalletList/implementation/BiometricUserWalletsListManagerTest.kt +++ b/app/src/test/kotlin/com/tangem/tap/domain/userWalletList/implementation/BiometricUserWalletsListManagerTest.kt @@ -23,6 +23,7 @@ internal class BiometricUserWalletsListManagerTest(private val model: Model) { publicInformationRepository = mockk(), sensitiveInformationRepository = mockk(), selectedUserWalletRepository = mockk(), + dispatcherProvider = mockk(), ) @Test