diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/UserWalletsListManager.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/UserWalletsListManager.kt index dfd62c05f5..9e7619e7c5 100644 --- a/app/src/main/java/com/tangem/tap/domain/userWalletList/UserWalletsListManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/UserWalletsListManager.kt @@ -19,7 +19,7 @@ interface UserWalletsListManager { suspend fun selectWallet(userWalletId: UserWalletId): CompletionResult /** - * Save user wallet + * Save provided user wallet and set it as selected * @param userWallet [UserWallet] to save * @param canOverride If false, then terminate with [UserWalletListError.WalletAlreadySaved] when user tries to save an * already saved card @@ -27,6 +27,17 @@ interface UserWalletsListManager { * */ suspend fun save(userWallet: UserWallet, canOverride: Boolean = false): CompletionResult + /** + * Same as [save] but not change selected user wallet ID + * and not terminate with [UserWalletListError.WalletAlreadySaved] if [UserWallet] already saved + * + * Can terminate with [NoSuchElementException] if unable to find [UserWallet] with provided [UserWalletId] + * @param userWalletId update [UserWallet] with that [UserWalletId] + * @param update lambda that receives stored [UserWallet] and returns updated [UserWallet] + * @return [CompletionResult] of operation with updated [UserWallet] + * */ + suspend fun update(userWalletId: UserWalletId, update: (UserWallet) -> UserWallet): CompletionResult + suspend fun delete(userWalletIds: List): CompletionResult suspend fun clear(): CompletionResult 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 4eef3db4b7..db1db2ebbd 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 @@ -10,6 +10,7 @@ import com.tangem.tap.domain.userWalletList.repository.SelectedUserWalletReposit import com.tangem.tap.domain.userWalletList.repository.UserWalletsKeysRepository import com.tangem.tap.domain.userWalletList.repository.UserWalletsPublicInformationRepository import com.tangem.tap.domain.userWalletList.repository.UserWalletsSensitiveInformationRepository +import com.tangem.tap.domain.userWalletList.utils.encryptionKey import com.tangem.tap.domain.userWalletList.utils.toUserWallets import com.tangem.tap.domain.userWalletList.utils.updateWith import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -84,7 +85,7 @@ internal class BiometricUserWalletsListManager( override suspend fun save(userWallet: UserWallet, canOverride: Boolean): CompletionResult { return if (canOverride) { - saveInternal(userWallet) + saveInternal(userWallet, changeSelectedUserWallet = true) } else { val isWalletSaved = state.value.userWallets .any { @@ -94,11 +95,25 @@ internal class BiometricUserWalletsListManager( if (isWalletSaved) { CompletionResult.Failure(UserWalletListError.WalletAlreadySaved) } else { - saveInternal(userWallet) + saveInternal(userWallet, changeSelectedUserWallet = true) } } } + override suspend fun update( + userWalletId: UserWalletId, + update: (UserWallet) -> UserWallet, + ): CompletionResult { + return get(userWalletId) + .map { storedUserWallet -> + update(storedUserWallet) + } + .flatMap { updatedUserWallet -> + saveInternal(updatedUserWallet, changeSelectedUserWallet = false) + .map { updatedUserWallet } + } + } + override suspend fun delete(userWalletIds: List): CompletionResult { if (userWalletIds.isEmpty()) { return CompletionResult.Success(Unit) @@ -111,9 +126,12 @@ internal class BiometricUserWalletsListManager( .flatMap { keysRepository.delete(userWalletIds) } .map { state.update { prevState -> + val newUserWallets = prevState.userWallets.filter { it.walletId !in userWalletIds } + prevState.copy( encryptionKeys = prevState.encryptionKeys.filter { it.walletId !in userWalletIds }, - userWallets = prevState.userWallets.filter { it.walletId !in userWalletIds }, + userWallets = newUserWallets, + isLocked = newUserWallets.any { it.isLocked }, ) } } @@ -135,27 +153,23 @@ internal class BiometricUserWalletsListManager( } } - private suspend fun saveInternal(userWallet: UserWallet): CompletionResult { - val newEncryptionKeys = state.value.encryptionKeys - .plus(UserWalletEncryptionKey(userWallet)) - .distinctBy { it.walletId } - - return keysRepository.store(newEncryptionKeys) - .doOnSuccess { - state.update { prevState -> - prevState.copy( - encryptionKeys = newEncryptionKeys, - selectedUserWalletId = userWallet.walletId, - ) - } - } + private suspend fun saveInternal( + userWallet: UserWallet, + changeSelectedUserWallet: Boolean, + ): CompletionResult { + return saveEncryptionKeyIfNotNull(userWallet) + .flatMap { sensitiveInformationRepository.save(userWallet, encryptionKey = it) } .flatMap { publicInformationRepository.save(userWallet) } - .flatMap { sensitiveInformationRepository.save(userWallet) } .map { selectedUserWalletRepository.set(userWallet.walletId) } .flatMap { loadModels() } .doOnSuccess { state.update { prevState -> prevState.copy( + selectedUserWalletId = if (changeSelectedUserWallet) { + userWallet.walletId + } else { + prevState.selectedUserWalletId + }, isLocked = prevState.userWallets.any { it.isLocked }, ) } @@ -181,6 +195,27 @@ internal class BiometricUserWalletsListManager( } } + private suspend fun saveEncryptionKeyIfNotNull(userWallet: UserWallet): CompletionResult { + val encryptionKey = userWallet.scanResponse.card.encryptionKey + ?.let { UserWalletEncryptionKey(userWallet.walletId, it) } + + return if (encryptionKey != null) { + keysRepository.save(encryptionKey) + .doOnSuccess { + state.update { prevState -> + prevState.copy( + encryptionKeys = prevState.encryptionKeys + .plus(encryptionKey) + .distinctBy { it.walletId }, + ) + } + } + .map { encryptionKey.encryptionKey } + } else { + CompletionResult.Success(data = null) + } + } + private suspend fun loadModels(): CompletionResult { return getSavedUserWallets() .map { userWallets -> diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/DummyUserWalletsListManager.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/DummyUserWalletsListManager.kt deleted file mode 100644 index e616c48758..0000000000 --- a/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/DummyUserWalletsListManager.kt +++ /dev/null @@ -1,56 +0,0 @@ -package com.tangem.tap.domain.userWalletList.implementation - -import com.tangem.common.CompletionResult -import com.tangem.common.catching -import com.tangem.domain.common.util.UserWalletId -import com.tangem.tap.domain.model.UserWallet -import com.tangem.tap.domain.userWalletList.UserWalletsListManager -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.flowOf - -class DummyUserWalletsListManager : UserWalletsListManager { - override val userWallets: Flow> - get() = flowOf(emptyList()) - override val selectedUserWallet: Flow - get() = flowOf() - override val selectedUserWalletSync: UserWallet? - get() = null - override val isLocked: Flow - get() = flowOf(true) - override val isLockedSync: Boolean - get() = true - override val hasSavedUserWallets: Boolean - get() = false - - override suspend fun unlockWithBiometry(): CompletionResult { - return CompletionResult.Success(null) - } - - override fun lock() { - /* no-op */ - } - - override suspend fun selectWallet(userWalletId: UserWalletId): CompletionResult { - return catching { - error("Not implemented") - } - } - - override suspend fun save(userWallet: UserWallet, canOverride: Boolean): CompletionResult { - return CompletionResult.Success(Unit) - } - - override suspend fun delete(userWalletIds: List): CompletionResult { - return CompletionResult.Success(Unit) - } - - override suspend fun clear(): CompletionResult { - return CompletionResult.Success(Unit) - } - - override suspend fun get(userWalletId: UserWalletId): CompletionResult { - return catching { - error("Not implemented") - } - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/model/UserWalletEncryptionKey.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/model/UserWalletEncryptionKey.kt index a5c20a7790..df0f473bae 100644 --- a/app/src/main/java/com/tangem/tap/domain/userWalletList/model/UserWalletEncryptionKey.kt +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/model/UserWalletEncryptionKey.kt @@ -2,19 +2,12 @@ package com.tangem.tap.domain.userWalletList.model import com.squareup.moshi.JsonClass import com.tangem.domain.common.util.UserWalletId -import com.tangem.tap.domain.model.UserWallet -import com.tangem.tap.domain.userWalletList.utils.encryptionKey @JsonClass(generateAdapter = true) internal data class UserWalletEncryptionKey( val walletId: UserWalletId, val encryptionKey: ByteArray, ) { - constructor(userWallet: UserWallet) : this( - walletId = userWallet.walletId, - encryptionKey = userWallet.scanResponse.card.encryptionKey, - ) - override fun equals(other: Any?): Boolean { if (this === other) return true if (other !is UserWalletEncryptionKey) return false diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/UserWalletsKeysRepository.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/UserWalletsKeysRepository.kt index bf96081cd4..99b62dc99d 100644 --- a/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/UserWalletsKeysRepository.kt +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/UserWalletsKeysRepository.kt @@ -14,11 +14,11 @@ internal interface UserWalletsKeysRepository { suspend fun getAll(): CompletionResult> /** - * Store the encryption keys for user wallets. Biometric authentication not required - * @param encryptionKeys List of encryption keys for user wallets + * Save the encryption key for user wallet. Biometric authentication not required + * @param encryptionKey [UserWalletEncryptionKey] to save * @return [CompletionResult] of operation * */ - suspend fun store(encryptionKeys: List): CompletionResult + suspend fun save(encryptionKey: UserWalletEncryptionKey): CompletionResult /** * Delete encryption keys for user wallets. Biometric authentication not required diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/UserWalletsSensitiveInformationRepository.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/UserWalletsSensitiveInformationRepository.kt index 7e03055150..fdae3e64a0 100644 --- a/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/UserWalletsSensitiveInformationRepository.kt +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/UserWalletsSensitiveInformationRepository.kt @@ -7,7 +7,7 @@ import com.tangem.tap.domain.userWalletList.model.UserWalletEncryptionKey import com.tangem.tap.domain.userWalletList.model.UserWalletSensitiveInformation internal interface UserWalletsSensitiveInformationRepository { - suspend fun save(userWallet: UserWallet): CompletionResult + suspend fun save(userWallet: UserWallet, encryptionKey: ByteArray?): CompletionResult suspend fun getAll( encryptionKeys: List, ): CompletionResult> 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 8de5a59457..11e558535d 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 @@ -45,10 +45,9 @@ internal class BiometricUserWalletsKeysRepository( } } - override suspend fun store(encryptionKeys: List): CompletionResult { + override suspend fun save(encryptionKey: UserWalletEncryptionKey): CompletionResult { return withContext(Dispatchers.IO) { - encryptionKeys.map { storeEncryptionKey(it) } - .fold() + storeEncryptionKey(encryptionKey) .mapFailure { error -> UserWalletListError.SaveEncryptionKeysError(error.cause ?: error) } diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/implementation/DefaultUserWalletsSensitiveInformationRepository.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/implementation/DefaultUserWalletsSensitiveInformationRepository.kt index 158dc0db2b..673ec51a10 100644 --- a/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/implementation/DefaultUserWalletsSensitiveInformationRepository.kt +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/implementation/DefaultUserWalletsSensitiveInformationRepository.kt @@ -38,11 +38,12 @@ internal class DefaultUserWalletsSensitiveInformationRepository( Cipher.getInstance("$algorithm/$blockMode/$encryptionPadding") } - override suspend fun save(userWallet: UserWallet): CompletionResult { + override suspend fun save(userWallet: UserWallet, encryptionKey: ByteArray?): CompletionResult { + if (encryptionKey == null) return CompletionResult.Success(Unit) // Encryption key is null, do nothing return catching { val encryptedSensitiveInformation = userWallet.sensitiveInformation .encode() - .encryptAndStoreIv(userWallet.walletId.stringValue, userWallet.scanResponse.card.encryptionKey) + .encryptAndStoreIv(userWallet.walletId.stringValue, encryptionKey) getAllEncrypted().toMutableMap() .apply { set(userWallet.walletId.stringValue, encryptedSensitiveInformation) } diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/utils/UserWalletEncyptionKeyCalculator.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/utils/UserWalletEncyptionKeyCalculator.kt index 3ecfb70521..d97af026cc 100644 --- a/app/src/main/java/com/tangem/tap/domain/userWalletList/utils/UserWalletEncyptionKeyCalculator.kt +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/utils/UserWalletEncyptionKeyCalculator.kt @@ -4,15 +4,8 @@ import com.tangem.common.extensions.calculateSha256 import com.tangem.domain.common.CardDTO import com.tangem.domain.common.extensions.calculateHmacSha256 -internal val CardDTO.encryptionKey: ByteArray - @Throws(IllegalArgumentException::class) - get() { - val walletPublicKey = requireNotNull(findPublicKey(wallets)) { - "Wallet public key must not be null" - } - - return calculateEncryptionKey(walletPublicKey) - } +internal val CardDTO.encryptionKey: ByteArray? + get() = findPublicKey(wallets)?.let { calculateEncryptionKey(it) } private fun calculateEncryptionKey(publicKey: ByteArray): ByteArray { val message = MESSAGE_FOR_ENCRYPTION_KEY.toByteArray() diff --git a/app/src/main/java/com/tangem/tap/features/tokens/redux/TokensMiddleware.kt b/app/src/main/java/com/tangem/tap/features/tokens/redux/TokensMiddleware.kt index b07a4fd3ca..10a7d4e03a 100644 --- a/app/src/main/java/com/tangem/tap/features/tokens/redux/TokensMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/tokens/redux/TokensMiddleware.kt @@ -281,13 +281,16 @@ class TokensMiddleware { ) { val selectedUserWallet = userWalletsListManager.selectedUserWalletSync if (selectedUserWallet != null) { - val updatedUserWallet = selectedUserWallet.copy( - scanResponse = scanResponse, - ) - scope.launch { - userWalletsListManager.save(updatedUserWallet, canOverride = true) - .flatMap { + userWalletsListManager.update( + userWalletId = selectedUserWallet.walletId, + update = { userWallet -> + userWallet.copy( + scanResponse = scanResponse, + ) + }, + ) + .flatMap { updatedUserWallet -> walletCurrenciesManager.addCurrencies( userWallet = updatedUserWallet, currenciesToAdd = currencyList, diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/MultiWalletMiddleware.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/MultiWalletMiddleware.kt index 9bba0011a5..821f4971b4 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/MultiWalletMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/MultiWalletMiddleware.kt @@ -179,21 +179,24 @@ class MultiWalletMiddleware { } private fun scanAndUpdateCard( - selectedWallet: UserWallet, + selectedUserWallet: UserWallet, state: WalletState?, ) = scope.launch { Analytics.send(MainScreen.CardWasScanned()) ScanCardProcessor.scan( - cardId = selectedWallet.cardId, + cardId = selectedUserWallet.cardId, additionalBlockchainsToDerive = state?.missingDerivations?.map { it.blockchain }, ) { scanResponse -> - val userWallet = selectedWallet.copy( - scanResponse = scanResponse, + userWalletsListManager.update( + userWalletId = selectedUserWallet.walletId, + update = { userWallet -> + userWallet.copy( + scanResponse = scanResponse, + ) + }, ) - - userWalletsListManager.save(userWallet, canOverride = true) - .doOnSuccess { - store.state.globalState.tapWalletManager.loadData(userWallet, refresh = true) + .doOnSuccess { updatedUserWallet -> + store.state.globalState.tapWalletManager.loadData(updatedUserWallet, refresh = true) } } } diff --git a/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorMiddleware.kt b/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorMiddleware.kt index c1f24294f4..4772b501d7 100644 --- a/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorMiddleware.kt @@ -239,9 +239,7 @@ internal class WalletSelectorMiddleware { Analytics.send(MyWallets.Button.EditWalletTapped) scope.launch { - userWalletsListManager.get(userWalletId) - .map { it.copy(name = newName) } - .flatMap { userWalletsListManager.save(it, canOverride = true) } + userWalletsListManager.update(userWalletId) { it.copy(name = newName) } .doOnFailure { error -> store.dispatchOnMain(WalletSelectorAction.HandleError(error)) } diff --git a/app/src/main/java/com/tangem/tap/features/walletSelector/ui/WalletSelectorViewModel.kt b/app/src/main/java/com/tangem/tap/features/walletSelector/ui/WalletSelectorViewModel.kt index 1192b37c4a..52a99013e3 100644 --- a/app/src/main/java/com/tangem/tap/features/walletSelector/ui/WalletSelectorViewModel.kt +++ b/app/src/main/java/com/tangem/tap/features/walletSelector/ui/WalletSelectorViewModel.kt @@ -38,7 +38,6 @@ internal class WalletSelectorViewModel : ViewModel(), StoreSubscriber Unit editingUserWalletsIds.isNotEmpty() && !editingUserWalletsIds.contains(userWalletId) -> { editWallet(userWalletId) } @@ -52,7 +51,7 @@ internal class WalletSelectorViewModel : ViewModel(), StoreSubscriber - store.dispatch(WalletSelectorAction.RenameWallet(editedWalletId, newName)) + store.dispatch(WalletSelectorAction.RenameWallet(editedUserWalletId, newName)) stateInternal.update { prevState -> prevState.copy( renameWalletDialog = null, @@ -137,11 +136,6 @@ internal class WalletSelectorViewModel : ViewModel(), StoreSubscriber appState.skip { old, new -> old.walletSelectorState == new.walletSelectorState }