Updated on 2026-08-14

This commit is contained in:
Tangem 2026-02-06 11:13:31 +04:00
parent 8ba59403f4
commit 18d4a01642
7 changed files with 159 additions and 0 deletions

View file

@ -398,4 +398,12 @@ internal object WalletsDomainModule {
fun provideSyncWalletWithRemoteUseCase(walletsRepository: WalletsRepository): SyncWalletWithRemoteUseCase { fun provideSyncWalletWithRemoteUseCase(walletsRepository: WalletsRepository): SyncWalletWithRemoteUseCase {
return SyncWalletWithRemoteUseCase(walletsRepository = walletsRepository) return SyncWalletWithRemoteUseCase(walletsRepository = walletsRepository)
} }
@Provides
@Singleton
fun provideApplyUserWalletListSortingUseCase(
userWalletsListRepository: UserWalletsListRepository,
): ApplyUserWalletListSortingUseCase {
return ApplyUserWalletListSortingUseCase(userWalletsListRepository = userWalletsListRepository)
}
} }

View file

@ -13,6 +13,7 @@ import com.tangem.core.analytics.utils.TrackingContextProxy
import com.tangem.datasource.local.preferences.AppPreferencesStore import com.tangem.datasource.local.preferences.AppPreferencesStore
import com.tangem.datasource.local.preferences.PreferencesKeys import com.tangem.datasource.local.preferences.PreferencesKeys
import com.tangem.datasource.local.preferences.utils.getSyncOrDefault import com.tangem.datasource.local.preferences.utils.getSyncOrDefault
import com.tangem.domain.common.wallets.UserWalletTransformAction
import com.tangem.domain.common.wallets.UserWalletsListRepository import com.tangem.domain.common.wallets.UserWalletsListRepository
import com.tangem.domain.common.wallets.UserWalletsListRepository.LockMethod import com.tangem.domain.common.wallets.UserWalletsListRepository.LockMethod
import com.tangem.domain.common.wallets.error.* import com.tangem.domain.common.wallets.error.*
@ -31,6 +32,8 @@ import com.tangem.sdk.api.TangemSdkManager
import com.tangem.tap.domain.userWalletList.model.UserWalletEncryptionKey import com.tangem.tap.domain.userWalletList.model.UserWalletEncryptionKey
import com.tangem.tap.domain.userWalletList.utils.encryptionKey import com.tangem.tap.domain.userWalletList.utils.encryptionKey
import com.tangem.tap.domain.userWalletList.utils.lock import com.tangem.tap.domain.userWalletList.utils.lock
import com.tangem.tap.domain.userWalletList.utils.publicInformation
import com.tangem.tap.domain.userWalletList.utils.sensitiveInformation
import com.tangem.tap.domain.userWalletList.utils.toUserWallets import com.tangem.tap.domain.userWalletList.utils.toUserWallets
import com.tangem.tap.domain.userWalletList.utils.updateWith import com.tangem.tap.domain.userWalletList.utils.updateWith
import com.tangem.utils.Provider import com.tangem.utils.Provider
@ -38,10 +41,12 @@ import com.tangem.utils.ProviderSuspend
import com.tangem.utils.coroutines.runSuspendCatching import com.tangem.utils.coroutines.runSuspendCatching
import com.tangem.utils.extensions.addOrReplace import com.tangem.utils.extensions.addOrReplace
import com.tangem.utils.extensions.indexOfFirstOrNull import com.tangem.utils.extensions.indexOfFirstOrNull
import kotlinx.coroutines.NonCancellable
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.update import kotlinx.coroutines.flow.update
import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock import kotlinx.coroutines.sync.withLock
import kotlinx.coroutines.withContext
@Suppress("LongParameterList", "LargeClass") @Suppress("LongParameterList", "LargeClass")
internal class DefaultUserWalletsListRepository( internal class DefaultUserWalletsListRepository(
@ -384,6 +389,44 @@ internal class DefaultUserWalletsListRepository(
return userWallets.any { it.walletId !in unsecuredWalletIds } return userWallets.any { it.walletId !in unsecuredWalletIds }
} }
override suspend fun transform(action: UserWalletTransformAction) {
val wallets = userWalletsSync()
val walletsMap = wallets.associateBy { it.walletId }
val transformedWallets = action.transform(wallets)
val transformedWalletsMap = transformedWallets.associateBy { it.walletId }
require(walletsMap.keys == transformedWalletsMap.keys) {
"The transformation action must not change the set of wallet IDs." +
"Original IDs: ${walletsMap.keys}, Transformed IDs: ${transformedWalletsMap.keys}"
}
updateWallets { transformedWallets }
withContext(NonCancellable) {
if (savePersistentInformation()) {
publicInformationRepository.transform {
transformedWallets.map { wallet -> wallet.publicInformation }
}
val changedSensitiveInfoWallets = wallets.filter { wallet ->
val transformedWallet = transformedWalletsMap[wallet.walletId]
transformedWallet != null && transformedWallet.sensitiveInformation != wallet.sensitiveInformation
}
changedSensitiveInfoWallets.forEach { wallet ->
if (wallet.isLocked.not()) {
sensitiveInformationRepository.save(wallet, wallet.encryptionKey)
}
checkForUpgradeAndDeleteHotWalletIfNeeded(
newUserWallet = wallet,
oldUserWallet = walletsMap[wallet.walletId] ?: error("This should never happen"),
)
}
}
}
}
private suspend fun checkForUpgradeAndDeleteHotWalletIfNeeded( private suspend fun checkForUpgradeAndDeleteHotWalletIfNeeded(
newUserWallet: UserWallet, newUserWallet: UserWallet,
oldUserWallet: UserWallet, oldUserWallet: UserWallet,

View file

@ -12,4 +12,8 @@ internal interface UserWalletsPublicInformationRepository {
suspend fun delete(walletIds: List<UserWalletId>): CompletionResult<Unit> suspend fun delete(walletIds: List<UserWalletId>): CompletionResult<Unit>
suspend fun clear(): CompletionResult<Unit> suspend fun clear(): CompletionResult<Unit>
suspend fun transform(
block: (List<UserWalletPublicInformation>?) -> List<UserWalletPublicInformation>?,
): CompletionResult<Unit>
} }

View file

@ -77,6 +77,21 @@ internal class DefaultUserWalletsPublicInformationRepository(
} }
} }
override suspend fun transform(
block: (List<UserWalletPublicInformation>?) -> List<UserWalletPublicInformation>?,
): CompletionResult<Unit> {
return withContext(Dispatchers.IO) {
getAll().flatMap { currentInfo ->
val transformed = block(currentInfo)
if (transformed != null) {
save(transformed)
} else {
CompletionResult.Success(Unit)
}
}
}
}
@JvmName("saveWithPublicInformation") @JvmName("saveWithPublicInformation")
private suspend fun save(publicInformation: List<UserWalletPublicInformation>): CompletionResult<Unit> = catching { private suspend fun save(publicInformation: List<UserWalletPublicInformation>): CompletionResult<Unit> = catching {
withContext(Dispatchers.IO) { withContext(Dispatchers.IO) {

View file

@ -0,0 +1,35 @@
package com.tangem.domain.common.wallets
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.models.wallet.UserWalletId
/**
* Sealed class representing actions that can transform the user wallets list.
*/
sealed class UserWalletTransformAction {
/**
* The transformation block to apply to the current wallets list.
*/
abstract fun transform(list: List<UserWallet>): List<UserWallet>
/**
* Reorders user wallets
*
* @param action The transformation block that takes the current list of user wallets
* and returns a list of user wallet IDs in the desired order.
*/
data class Reorder(
private val action: (List<UserWallet>) -> List<UserWalletId>,
) : UserWalletTransformAction() {
override fun transform(list: List<UserWallet>): List<UserWallet> {
val walletById = list.associateBy { it.walletId }
val newOrderIds = action(list)
require(walletById.keys == newOrderIds.toSet()) {
"Reorder action must return the same set of wallet IDs as the input list"
}
return newOrderIds.mapNotNull { walletById[it] }
}
}
}

View file

@ -123,6 +123,14 @@ interface UserWalletsListRepository {
*/ */
suspend fun clearPersistentData() suspend fun clearPersistentData()
/**
* Transforms user wallets list according to the provided action.
*
* @param action The transformation action to apply.
* @throws IllegalArgumentException if the transformation action is invalid (e.g. reordering with missing or extra wallet IDs).
*/
suspend fun transform(action: UserWalletTransformAction)
/** /**
* Checks if there are any secured wallets (wallets that are not locked with [LockMethod.NoLock]). * Checks if there are any secured wallets (wallets that are not locked with [LockMethod.NoLock]).
*/ */

View file

@ -0,0 +1,46 @@
package com.tangem.domain.wallets.usecase
import arrow.core.Either
import arrow.core.raise.catch
import arrow.core.raise.either
import arrow.core.raise.ensure
import com.tangem.domain.common.wallets.UserWalletTransformAction
import com.tangem.domain.common.wallets.UserWalletsListRepository
import com.tangem.domain.models.wallet.UserWalletId
/**
* Use case to apply a specific sorting order to a list of user wallets.
*
* @property userWalletsListRepository Repository for managing user wallets list.
*/
class ApplyUserWalletListSortingUseCase(
private val userWalletsListRepository: UserWalletsListRepository,
) {
/**
* Applies the sorting order of the provided list of user wallet IDs.
*
* @param userWalletIds List of [UserWalletId] representing the desired order.
* @return Either an [Error] or Unit on successful completion.
*/
suspend operator fun invoke(userWalletIds: List<UserWalletId>): Either<Error, Unit> = either {
ensure(userWalletIds.size > 1) { Error.UnableToSortSingleWallet }
catch(
block = {
userWalletsListRepository.transform(UserWalletTransformAction.Reorder { userWalletIds })
},
catch = { raise(Error.ReorderError) },
)
}
/**
* Sealed interface representing possible errors that can occur during the application
* of user wallet list sorting.
*/
sealed interface Error {
data object UnableToSortSingleWallet : Error
data object ReorderError : Error
}
}