Updated on 2026-08-14
This commit is contained in:
commit
d994b7840a
150 changed files with 971 additions and 4621 deletions
|
|
@ -16,19 +16,14 @@ import com.tangem.domain.visa.model.VisaActivationRemoteState
|
|||
import com.tangem.domain.visa.model.VisaCardActivationStatus
|
||||
import com.tangem.domain.wallets.hot.HotWalletAccessCodeAttemptsRepository
|
||||
import com.tangem.domain.wallets.hot.HotWalletPasswordRequester
|
||||
import com.tangem.domain.wallets.legacy.UserWalletsListManager
|
||||
import com.tangem.hot.sdk.TangemHotSdk
|
||||
import com.tangem.sdk.storage.AndroidSecureStorage
|
||||
import com.tangem.sdk.storage.AndroidSecureStorageV2
|
||||
import com.tangem.sdk.storage.createEncryptedSharedPreferences
|
||||
import com.tangem.tap.domain.userWalletList.implementation.BiometricUserWalletsListManager
|
||||
import com.tangem.tap.domain.userWalletList.implementation.GeneralUserWalletsListManager
|
||||
import com.tangem.tap.domain.userWalletList.implementation.RuntimeUserWalletsListManager
|
||||
import com.tangem.tap.domain.userWalletList.repository.DefaultUserWalletsListRepository
|
||||
import com.tangem.tap.domain.userWalletList.repository.DelegatedKeystoreManager
|
||||
import com.tangem.tap.domain.userWalletList.repository.UserWalletEncryptionKeysRepository
|
||||
import com.tangem.tap.domain.userWalletList.repository.UserWalletsKeysStoreDecorator
|
||||
import com.tangem.tap.domain.userWalletList.repository.implementation.BiometricUserWalletsKeysRepository
|
||||
import com.tangem.tap.domain.userWalletList.repository.implementation.DefaultSelectedUserWalletRepository
|
||||
import com.tangem.tap.domain.userWalletList.repository.implementation.DefaultUserWalletsPublicInformationRepository
|
||||
import com.tangem.tap.domain.userWalletList.repository.implementation.DefaultUserWalletsSensitiveInformationRepository
|
||||
|
|
@ -47,77 +42,6 @@ import javax.inject.Singleton
|
|||
@InstallIn(SingletonComponent::class)
|
||||
internal object UserWalletsListManagerModule {
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
@Deprecated("Use UserWalletsListRepository instead")
|
||||
fun provideGeneralUserWalletsListManager(
|
||||
@ApplicationContext applicationContext: Context,
|
||||
appPreferencesStore: AppPreferencesStore,
|
||||
dispatchers: CoroutineDispatcherProvider,
|
||||
analyticsEventHandler: AnalyticsEventHandler,
|
||||
): UserWalletsListManager {
|
||||
return GeneralUserWalletsListManager(
|
||||
runtimeUserWalletsListManager = RuntimeUserWalletsListManager(),
|
||||
biometricUserWalletsListManager = createBiometricUserWalletsListManager(
|
||||
applicationContext = applicationContext,
|
||||
analyticsEventHandler = analyticsEventHandler,
|
||||
dispatchers = dispatchers,
|
||||
),
|
||||
appPreferencesStore = appPreferencesStore,
|
||||
dispatchers = dispatchers,
|
||||
)
|
||||
}
|
||||
|
||||
@Deprecated("Use UserWalletsListRepository instead")
|
||||
private fun createBiometricUserWalletsListManager(
|
||||
applicationContext: Context,
|
||||
analyticsEventHandler: AnalyticsEventHandler,
|
||||
dispatchers: CoroutineDispatcherProvider,
|
||||
): UserWalletsListManager {
|
||||
val moshi = buildMoshi()
|
||||
val secureStorage = buildSecureStorage(applicationContext = applicationContext)
|
||||
|
||||
val authenticatedStorage = AuthenticatedStorage(
|
||||
secureStorage = UserWalletsKeysStoreDecorator(
|
||||
featureStorage = secureStorage,
|
||||
cardSdkStorageProvider = Provider { tangemSdkManager.secureStorage },
|
||||
),
|
||||
keystoreManager = DelegatedKeystoreManager(
|
||||
keystoreManagerProvider = Provider { tangemSdkManager.keystoreManager },
|
||||
),
|
||||
)
|
||||
|
||||
val keysRepository = BiometricUserWalletsKeysRepository(
|
||||
moshi = moshi,
|
||||
secureStorage = secureStorage,
|
||||
authenticatedStorage = authenticatedStorage,
|
||||
analyticsEventHandler = analyticsEventHandler,
|
||||
)
|
||||
|
||||
val publicInformationRepository = DefaultUserWalletsPublicInformationRepository(
|
||||
moshi = moshi,
|
||||
secureStorage = secureStorage,
|
||||
)
|
||||
|
||||
val sensitiveInformationRepository = DefaultUserWalletsSensitiveInformationRepository(
|
||||
moshi = moshi,
|
||||
secureStorage = secureStorage,
|
||||
)
|
||||
|
||||
val selectedUserWalletRepository = DefaultSelectedUserWalletRepository(
|
||||
secureStorage = secureStorage,
|
||||
dispatchers = dispatchers,
|
||||
)
|
||||
|
||||
return BiometricUserWalletsListManager(
|
||||
keysRepository = keysRepository,
|
||||
publicInformationRepository = publicInformationRepository,
|
||||
sensitiveInformationRepository = sensitiveInformationRepository,
|
||||
selectedUserWalletRepository = selectedUserWalletRepository,
|
||||
dispatcherProvider = dispatchers,
|
||||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideUserWalletsListRepository(
|
||||
|
|
@ -183,7 +107,7 @@ internal object UserWalletsListManagerModule {
|
|||
)
|
||||
}
|
||||
|
||||
fun buildMoshi(): Moshi {
|
||||
private fun buildMoshi(): Moshi {
|
||||
return Moshi.Builder()
|
||||
.add(WalletDerivedKeysMapAdapter())
|
||||
.add(ScanResponseDerivedKeysMapAdapter())
|
||||
|
|
@ -200,7 +124,7 @@ internal object UserWalletsListManagerModule {
|
|||
.build()
|
||||
}
|
||||
|
||||
fun buildSecureStorage(@ApplicationContext applicationContext: Context): SecureStorage {
|
||||
private fun buildSecureStorage(@ApplicationContext applicationContext: Context): SecureStorage {
|
||||
return AndroidSecureStorage(
|
||||
preferences = SecureStorage.createEncryptedSharedPreferences(
|
||||
context = applicationContext,
|
||||
|
|
|
|||
|
|
@ -1,434 +0,0 @@
|
|||
package com.tangem.tap.domain.userWalletList.implementation
|
||||
|
||||
import com.tangem.common.*
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.domain.models.wallet.isLocked
|
||||
import com.tangem.domain.wallets.legacy.UserWalletsListError
|
||||
import com.tangem.domain.wallets.legacy.UserWalletsListManager
|
||||
import com.tangem.domain.wallets.legacy.UserWalletsListManager.Lockable.UnlockType
|
||||
import com.tangem.tap.domain.userWalletList.model.UserWalletEncryptionKey
|
||||
import com.tangem.tap.domain.userWalletList.repository.SelectedUserWalletRepository
|
||||
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.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")
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
internal class BiometricUserWalletsListManager(
|
||||
private val keysRepository: UserWalletsKeysRepository,
|
||||
private val publicInformationRepository: UserWalletsPublicInformationRepository,
|
||||
private val sensitiveInformationRepository: UserWalletsSensitiveInformationRepository,
|
||||
private val selectedUserWalletRepository: SelectedUserWalletRepository,
|
||||
private val dispatcherProvider: CoroutineDispatcherProvider,
|
||||
) : UserWalletsListManager.Lockable {
|
||||
private val state = MutableStateFlow(State())
|
||||
|
||||
private var hasSavedWallets: Boolean? = null
|
||||
private val savedWalletMutex = Mutex()
|
||||
|
||||
override val isLockable: Boolean = true
|
||||
|
||||
override val userWallets: Flow<List<UserWallet>>
|
||||
get() = state
|
||||
.mapLatest { it.userWallets }
|
||||
.distinctUntilChanged()
|
||||
|
||||
override val userWalletsSync: List<UserWallet>
|
||||
get() = state.value.userWallets
|
||||
|
||||
@Deprecated("You should provide the selected wallet via routing parameters due to the scalability of the features")
|
||||
override val selectedUserWallet: Flow<UserWallet>
|
||||
get() = state
|
||||
.mapLatest { state ->
|
||||
findSelectedUserWallet(state.userWallets)
|
||||
}
|
||||
.filterNotNull()
|
||||
.distinctUntilChanged()
|
||||
|
||||
@Deprecated("You should provide the selected wallet via routing parameters due to the scalability of the features")
|
||||
override val selectedUserWalletSync: UserWallet?
|
||||
get() = findSelectedUserWallet()
|
||||
|
||||
override val lockedState: Flow<Boolean>
|
||||
get() = state
|
||||
.mapLatest { it.isLocked }
|
||||
.distinctUntilChanged()
|
||||
|
||||
override val isLocked: Boolean
|
||||
get() = state.value.isLocked
|
||||
|
||||
override val hasUserWallets: Boolean
|
||||
get() {
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override val walletsCount: Int
|
||||
get() = state.value.userWallets.size
|
||||
|
||||
override val savedWalletsCount: Flow<Int>
|
||||
get() = state
|
||||
.mapLatest { walletsCount }
|
||||
.distinctUntilChanged()
|
||||
|
||||
override suspend fun unlock(type: UnlockType): CompletionResult<UserWallet> {
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun lock() {
|
||||
state.update { prevState ->
|
||||
prevState.copy(
|
||||
encryptionKeys = emptyList(),
|
||||
userWallets = prevState.userWallets.lockAll(),
|
||||
isLocked = true,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun select(userWalletId: UserWalletId): CompletionResult<UserWallet> = catching {
|
||||
if (state.value.selectedUserWalletId == userWalletId) {
|
||||
return@catching requireNotNull(findSelectedUserWallet()) {
|
||||
"Wallet is not found"
|
||||
}
|
||||
}
|
||||
|
||||
selectedUserWalletRepository.set(userWalletId)
|
||||
|
||||
val newState = state.updateAndGet { prevState ->
|
||||
prevState.copy(
|
||||
selectedUserWalletId = userWalletId,
|
||||
)
|
||||
}
|
||||
|
||||
newState.userWallets.first { it.walletId == userWalletId }
|
||||
}
|
||||
|
||||
override suspend fun save(userWallet: UserWallet, canOverride: Boolean): CompletionResult<Unit> {
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun update(
|
||||
userWalletId: UserWalletId,
|
||||
update: suspend (UserWallet) -> UserWallet,
|
||||
): CompletionResult<UserWallet> {
|
||||
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<UserWalletId>): CompletionResult<Unit> {
|
||||
val idsToRemove = state.value.userWallets
|
||||
.takeIf { it.isNotEmpty() }
|
||||
?.filter { it.walletId in userWalletIds }
|
||||
?.map { it.walletId }
|
||||
|
||||
if (idsToRemove.isNullOrEmpty()) {
|
||||
return CompletionResult.Success(Unit)
|
||||
}
|
||||
|
||||
if (idsToRemove.size == state.value.userWallets.size) {
|
||||
return clear()
|
||||
}
|
||||
|
||||
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,
|
||||
)
|
||||
|
||||
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<Unit> {
|
||||
savedWalletMutex.withLock {
|
||||
hasSavedWallets = null
|
||||
}
|
||||
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<UserWallet> {
|
||||
return catching {
|
||||
state.value.userWallets.first { it.walletId == userWalletId }
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun saveInternal(
|
||||
userWallet: UserWallet,
|
||||
changeSelectedUserWallet: Boolean,
|
||||
canOverridePublicInfo: Boolean,
|
||||
): CompletionResult<Unit> {
|
||||
val encryptionKey = userWallet.encryptionKey
|
||||
?.let { UserWalletEncryptionKey(userWallet.walletId, it) }
|
||||
?: return CompletionResult.Success(Unit) // No encryption key, no need to save
|
||||
|
||||
return keysRepository.save(encryptionKey)
|
||||
.flatMap { sensitiveInformationRepository.save(userWallet, encryptionKey = encryptionKey.encryptionKey) }
|
||||
.flatMap { publicInformationRepository.save(userWallet, canOverridePublicInfo) }
|
||||
.flatMap {
|
||||
loadUserWallets(
|
||||
encryptionKeys = state.value.encryptionKeys
|
||||
.plus(encryptionKey)
|
||||
.distinctBy(UserWalletEncryptionKey::walletId),
|
||||
)
|
||||
}
|
||||
.doOnSuccess { loadedState ->
|
||||
if (changeSelectedUserWallet) {
|
||||
selectedUserWalletRepository.set(userWallet.walletId)
|
||||
|
||||
state.value = loadedState.copy(
|
||||
selectedUserWalletId = userWallet.walletId,
|
||||
)
|
||||
} else {
|
||||
state.value = loadedState
|
||||
}
|
||||
}
|
||||
.map { /* Type erasing */ }
|
||||
}
|
||||
|
||||
private suspend fun unlockAndSetSelectedUserWallet(type: UnlockType): CompletionResult<UserWallet?> {
|
||||
return keysRepository.getAll()
|
||||
.flatMap { encryptionKeys ->
|
||||
loadUserWallets(
|
||||
encryptionKeys = state.value.encryptionKeys
|
||||
.plus(encryptionKeys)
|
||||
.distinctBy(UserWalletEncryptionKey::walletId),
|
||||
)
|
||||
}
|
||||
.map { loadedState ->
|
||||
when (type) {
|
||||
UnlockType.ALL -> {
|
||||
if (loadedState.isLocked) {
|
||||
Timber.e("Some user wallets remain locked")
|
||||
|
||||
state.value = loadedState
|
||||
|
||||
throw UserWalletsListError.NotAllUserWalletsUnlocked
|
||||
} else {
|
||||
val prevState = state.value
|
||||
|
||||
val selectedWallet = findOrSetSelectedWallet(
|
||||
prevSelectedWalletId = prevState.selectedUserWalletId,
|
||||
userWallets = loadedState.userWallets,
|
||||
prevSelectedWalletIndex = prevState.userWallets.indexOfFirst {
|
||||
it.walletId == prevState.selectedUserWalletId
|
||||
},
|
||||
)
|
||||
|
||||
state.value = loadedState.copy(selectedUserWalletId = selectedWallet?.walletId)
|
||||
|
||||
selectedWallet
|
||||
}
|
||||
}
|
||||
UnlockType.ANY -> {
|
||||
val prevState = state.value
|
||||
|
||||
val selectedWallet = findOrSetSelectedWallet(
|
||||
prevSelectedWalletId = state.value.selectedUserWalletId,
|
||||
prevSelectedWalletIndex = prevState.userWallets.indexOfFirst {
|
||||
it.walletId == prevState.selectedUserWalletId
|
||||
},
|
||||
userWallets = loadedState.userWallets,
|
||||
)
|
||||
|
||||
state.value = loadedState.copy(selectedUserWalletId = selectedWallet?.walletId)
|
||||
|
||||
selectedWallet
|
||||
}
|
||||
UnlockType.ALL_WITHOUT_SELECT -> {
|
||||
state.value = loadedState
|
||||
|
||||
findSelectedUserWallet()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun loadUserWallets(encryptionKeys: List<UserWalletEncryptionKey>): CompletionResult<State> {
|
||||
return publicInformationRepository.getAll()
|
||||
.map { it.toUserWallets() }
|
||||
.flatMap { userWallets ->
|
||||
sensitiveInformationRepository.getAll(encryptionKeys)
|
||||
.map { walletIdToSensitiveInformation ->
|
||||
userWallets.updateWith(walletIdToSensitiveInformation)
|
||||
}
|
||||
}
|
||||
.map { userWallets ->
|
||||
val prevState = state.value
|
||||
|
||||
if (userWallets.isNotEmpty()) {
|
||||
val newUserWallets = (userWallets + prevState.userWallets)
|
||||
.distinctBy(UserWallet::walletId)
|
||||
|
||||
prevState.copy(
|
||||
userWallets = newUserWallets,
|
||||
encryptionKeys = encryptionKeys,
|
||||
isLocked = newUserWallets.any(UserWallet::isLocked),
|
||||
)
|
||||
} else {
|
||||
prevState
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun findOrSetSelectedWallet(
|
||||
prevSelectedWalletId: UserWalletId?,
|
||||
prevSelectedWalletIndex: Int,
|
||||
userWallets: List<UserWallet>,
|
||||
ignorePrevSelectedWallet: Boolean = false,
|
||||
): UserWallet? {
|
||||
var possibleSelectedUserWallet: UserWallet? = null
|
||||
|
||||
if (!ignorePrevSelectedWallet) {
|
||||
val selectedWalletId = prevSelectedWalletId ?: selectedUserWalletRepository.get()
|
||||
possibleSelectedUserWallet = findSelectedUserWallet(userWallets, selectedWalletId)
|
||||
}
|
||||
|
||||
if (possibleSelectedUserWallet == null || possibleSelectedUserWallet.isLocked) {
|
||||
possibleSelectedUserWallet =
|
||||
userWallets.findAvailableUserWallet(prevSelectedIndex = prevSelectedWalletIndex)
|
||||
}
|
||||
|
||||
selectedUserWalletRepository.set(possibleSelectedUserWallet?.walletId)
|
||||
|
||||
return possibleSelectedUserWallet
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the nearest available wallet that can be selected
|
||||
*
|
||||
* Example:
|
||||
* Number with *n* is previous selected wallet with index [prevSelectedIndex].
|
||||
*
|
||||
* 1. [*1*, 2, 3, 4] => delete 1 => [2, 3, 4] => find and select => [*2*, 3, 4]
|
||||
* 2. [1, *2*, 3, 4] => delete 2 => [1, 3, 4] => find and select => [1, *3*, 4]
|
||||
* 3. [1, 2, *3*, 4] => delete 3 => [1, 2, 4] => find and select => [1, 2, *4*]
|
||||
* 4. [1, 2, 3, *4*] => delete 4 => [1, 2, 3] => find and select => [1, 2, *3*]
|
||||
*
|
||||
* @receiver list of user wallets without deleted wallet
|
||||
*/
|
||||
private fun List<UserWallet>.findAvailableUserWallet(prevSelectedIndex: Int): UserWallet? {
|
||||
if (prevSelectedIndex == 0) return firstOrNull { !it.isLocked } ?: firstOrNull()
|
||||
|
||||
if (prevSelectedIndex in indices && !this[prevSelectedIndex].isLocked) return this[prevSelectedIndex]
|
||||
|
||||
for (offset in 1..size) {
|
||||
val rightIndex = prevSelectedIndex + offset
|
||||
if (rightIndex in indices && !this[rightIndex].isLocked) return this[rightIndex]
|
||||
|
||||
val leftIndex = prevSelectedIndex - offset
|
||||
if (leftIndex in indices && !this[leftIndex].isLocked) return this[leftIndex]
|
||||
}
|
||||
|
||||
return lastOrNull()
|
||||
}
|
||||
|
||||
private fun findSelectedUserWallet(
|
||||
userWallets: List<UserWallet> = state.value.userWallets,
|
||||
selectedUserWalletId: UserWalletId? = state.value.selectedUserWalletId,
|
||||
): UserWallet? {
|
||||
return userWallets.firstOrNull { it.walletId == selectedUserWalletId }
|
||||
}
|
||||
|
||||
private data class State(
|
||||
val encryptionKeys: List<UserWalletEncryptionKey> = emptyList(),
|
||||
val userWallets: List<UserWallet> = emptyList(),
|
||||
val selectedUserWalletId: UserWalletId? = null,
|
||||
val isLocked: Boolean = true,
|
||||
)
|
||||
}
|
||||
|
|
@ -1,203 +0,0 @@
|
|||
package com.tangem.tap.domain.userWalletList.implementation
|
||||
|
||||
import com.tangem.common.CompletionResult
|
||||
import com.tangem.datasource.local.preferences.AppPreferencesStore
|
||||
import com.tangem.datasource.local.preferences.PreferencesKeys
|
||||
import com.tangem.datasource.local.preferences.utils.get
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.domain.wallets.legacy.UserWalletsListManager
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.flow.*
|
||||
import timber.log.Timber
|
||||
|
||||
/**
|
||||
* General implementation of [UserWalletsListManager] that helps to switch between Runtime and Biometric
|
||||
* implementations.
|
||||
*
|
||||
* @property runtimeUserWalletsListManager runtime user wallets list manager
|
||||
* @property biometricUserWalletsListManager biometric user wallets list manager
|
||||
* @property appPreferencesStore app preferences store
|
||||
* @property dispatchers coroutine dispatcher provider
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
internal class GeneralUserWalletsListManager(
|
||||
private val runtimeUserWalletsListManager: UserWalletsListManager,
|
||||
private val biometricUserWalletsListManager: UserWalletsListManager,
|
||||
private val appPreferencesStore: AppPreferencesStore,
|
||||
private val dispatchers: CoroutineDispatcherProvider,
|
||||
) : UserWalletsListManager.Lockable {
|
||||
|
||||
private val applicationScope = CoroutineScope(dispatchers.io)
|
||||
private val implementation: MutableStateFlow<UserWalletsListManager?> = MutableStateFlow(value = null)
|
||||
|
||||
private val requireImplementation: UserWalletsListManager
|
||||
get() = requireNotNull(implementation.value) {
|
||||
"UserWalletsListManager is not initialized"
|
||||
}
|
||||
|
||||
init {
|
||||
subscribeOnCurrentManager()
|
||||
}
|
||||
|
||||
override val isLockable: Boolean
|
||||
get() = requireImplementation.isLockable
|
||||
|
||||
override val userWallets: Flow<List<UserWallet>>
|
||||
get() = implementation
|
||||
.transformLatest { impl ->
|
||||
if (impl != null) {
|
||||
emitAll(impl.userWallets)
|
||||
}
|
||||
}
|
||||
// To avoid returning empty flow to subscriber while implementation and userWallets are null
|
||||
// Flow is called first time when implementation is null and then when its assigned with implementation
|
||||
// that may have not user wallets (null or empty).
|
||||
// As a result subscription occurs on empty flow, than will not change if user wallets are available
|
||||
.filter { requireImplementation.hasUserWallets }
|
||||
|
||||
override val savedWalletsCount: Flow<Int>
|
||||
get() = implementation
|
||||
.transformLatest { impl ->
|
||||
if (impl != null) {
|
||||
emitAll(impl.savedWalletsCount)
|
||||
}
|
||||
}
|
||||
|
||||
override val userWalletsSync: List<UserWallet>
|
||||
get() = requireImplementation.userWalletsSync
|
||||
|
||||
override val selectedUserWallet: Flow<UserWallet>
|
||||
get() = implementation
|
||||
.transformLatest { impl ->
|
||||
if (impl != null) {
|
||||
emitAll(impl.selectedUserWallet)
|
||||
}
|
||||
}
|
||||
// To avoid returning empty flow to subscriber while implementation and userWallets are null
|
||||
// Flow is called first time when implementation is null and then when its assigned with implementation
|
||||
// that may have not user wallets (null or empty).
|
||||
// As a result subscription occurs on empty flow, than will not change if user wallets are available
|
||||
.filter { requireImplementation.hasUserWallets }
|
||||
|
||||
override val selectedUserWalletSync: UserWallet?
|
||||
get() = requireImplementation.selectedUserWalletSync
|
||||
|
||||
override val hasUserWallets: Boolean
|
||||
get() = requireImplementation.hasUserWallets
|
||||
|
||||
override val walletsCount: Int
|
||||
get() = requireImplementation.walletsCount
|
||||
|
||||
override val lockedState: Flow<Boolean>
|
||||
get() = implementation.transformLatest { impl ->
|
||||
if (impl == null) return@transformLatest
|
||||
|
||||
if (impl is UserWalletsListManager.Lockable) {
|
||||
emitAll(impl.lockedState)
|
||||
} else {
|
||||
error("RuntimeUserWalletsListManager is not lockable")
|
||||
}
|
||||
}
|
||||
|
||||
override val isLocked: Boolean
|
||||
get() {
|
||||
val impl = requireImplementation
|
||||
|
||||
return if (impl is UserWalletsListManager.Lockable) {
|
||||
impl.isLocked
|
||||
} else {
|
||||
error("RuntimeUserWalletsListManager is not lockable")
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun select(userWalletId: UserWalletId): CompletionResult<UserWallet> {
|
||||
return requireImplementation.select(userWalletId)
|
||||
}
|
||||
|
||||
override suspend fun save(userWallet: UserWallet, canOverride: Boolean): CompletionResult<Unit> {
|
||||
return requireImplementation.save(userWallet, canOverride)
|
||||
}
|
||||
|
||||
override suspend fun update(
|
||||
userWalletId: UserWalletId,
|
||||
update: suspend (UserWallet) -> UserWallet,
|
||||
): CompletionResult<UserWallet> {
|
||||
return requireImplementation.update(userWalletId, update)
|
||||
}
|
||||
|
||||
override suspend fun delete(userWalletIds: List<UserWalletId>): CompletionResult<Unit> {
|
||||
return requireImplementation.delete(userWalletIds)
|
||||
}
|
||||
|
||||
override suspend fun clear(): CompletionResult<Unit> {
|
||||
return requireImplementation.clear()
|
||||
}
|
||||
|
||||
override suspend fun get(userWalletId: UserWalletId): CompletionResult<UserWallet> {
|
||||
return requireImplementation.get(userWalletId)
|
||||
}
|
||||
|
||||
override suspend fun unlock(type: UserWalletsListManager.Lockable.UnlockType): CompletionResult<UserWallet> {
|
||||
val implementation = requireImplementation
|
||||
|
||||
return if (implementation is UserWalletsListManager.Lockable) {
|
||||
implementation.unlock(type)
|
||||
} else {
|
||||
error("RuntimeUserWalletsListManager is not lockable")
|
||||
}
|
||||
}
|
||||
|
||||
override fun lock() {
|
||||
val implementation = requireImplementation
|
||||
|
||||
return if (implementation is UserWalletsListManager.Lockable) {
|
||||
implementation.lock()
|
||||
} else {
|
||||
error("RuntimeUserWalletsListManager is not lockable")
|
||||
}
|
||||
}
|
||||
|
||||
private fun subscribeOnCurrentManager() {
|
||||
appPreferencesStore.get(key = PreferencesKeys.SAVE_USER_WALLETS_KEY, default = false)
|
||||
.distinctUntilChanged()
|
||||
.onEach { shouldSaveUserWallets ->
|
||||
val possibleManager = if (shouldSaveUserWallets) {
|
||||
biometricUserWalletsListManager
|
||||
} else {
|
||||
runtimeUserWalletsListManager
|
||||
}
|
||||
|
||||
if (possibleManager == implementation.value) {
|
||||
Timber.e("Switch to the same manager ${possibleManager::class.simpleName.orEmpty()}")
|
||||
}
|
||||
|
||||
Timber.i("Switch to ${possibleManager::class.simpleName.orEmpty()}")
|
||||
|
||||
val previousManager = implementation.value
|
||||
implementation.value = copySelectedUserWallet(
|
||||
sourceManager = previousManager,
|
||||
destinationManager = possibleManager,
|
||||
)
|
||||
|
||||
previousManager?.clear()
|
||||
}
|
||||
.flowOn(dispatchers.io)
|
||||
.launchIn(applicationScope)
|
||||
}
|
||||
|
||||
private suspend fun copySelectedUserWallet(
|
||||
sourceManager: UserWalletsListManager?,
|
||||
destinationManager: UserWalletsListManager,
|
||||
): UserWalletsListManager {
|
||||
sourceManager?.selectedUserWalletSync?.let { selectedWallet ->
|
||||
destinationManager.save(selectedWallet, canOverride = true)
|
||||
}
|
||||
|
||||
return destinationManager
|
||||
}
|
||||
}
|
||||
|
|
@ -1,115 +0,0 @@
|
|||
package com.tangem.tap.domain.userWalletList.implementation
|
||||
|
||||
import com.tangem.common.CompletionResult
|
||||
import com.tangem.common.catching
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.domain.wallets.legacy.UserWalletsListError
|
||||
import com.tangem.domain.wallets.legacy.UserWalletsListManager
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.flow.*
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
internal class RuntimeUserWalletsListManager : UserWalletsListManager {
|
||||
private val state = MutableStateFlow(State())
|
||||
|
||||
override val isLockable: Boolean = false
|
||||
|
||||
override val userWallets: Flow<List<UserWallet>>
|
||||
get() = state
|
||||
.mapLatest { listOfNotNull(it.userWallet) }
|
||||
.distinctUntilChanged()
|
||||
|
||||
override val selectedUserWallet: Flow<UserWallet>
|
||||
get() = state
|
||||
.mapLatest { it.userWallet }
|
||||
.filterNotNull()
|
||||
.distinctUntilChanged()
|
||||
|
||||
override val userWalletsSync: List<UserWallet>
|
||||
get() = listOfNotNull(state.value.userWallet)
|
||||
|
||||
override val selectedUserWalletSync: UserWallet?
|
||||
get() = state.value.userWallet
|
||||
|
||||
override val hasUserWallets: Boolean
|
||||
get() = state.value.userWallet != null
|
||||
|
||||
/**
|
||||
* only 1 wallet stored in runtime implementation
|
||||
*/
|
||||
override val walletsCount: Int
|
||||
get() = if (hasUserWallets) 1 else 0
|
||||
|
||||
override val savedWalletsCount: Flow<Int>
|
||||
get() = state
|
||||
.mapLatest { walletsCount }
|
||||
.distinctUntilChanged()
|
||||
|
||||
override suspend fun select(userWalletId: UserWalletId): CompletionResult<UserWallet> = catching {
|
||||
state.value.userWallet
|
||||
?.takeIf { it.walletId == userWalletId }
|
||||
?: walletNotFound()
|
||||
}
|
||||
|
||||
override suspend fun save(userWallet: UserWallet, canOverride: Boolean): CompletionResult<Unit> {
|
||||
return if (canOverride) {
|
||||
saveInternal(userWallet)
|
||||
} else {
|
||||
val isWalletSaved = state.value.userWallet?.walletId == userWallet.walletId
|
||||
|
||||
if (isWalletSaved) {
|
||||
CompletionResult.Failure(UserWalletsListError.WalletAlreadySaved)
|
||||
} else {
|
||||
saveInternal(userWallet)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun update(
|
||||
userWalletId: UserWalletId,
|
||||
update: suspend (UserWallet) -> UserWallet,
|
||||
): CompletionResult<UserWallet> = catching {
|
||||
val wallet = state.value.userWallet
|
||||
?.takeIf { it.walletId == userWalletId }
|
||||
?: walletNotFound()
|
||||
|
||||
requireNotNull(
|
||||
state.updateAndGet { prevState ->
|
||||
prevState.copy(
|
||||
userWallet = update(wallet),
|
||||
)
|
||||
}.userWallet,
|
||||
) { "User wallet is null after update" }
|
||||
}
|
||||
|
||||
override suspend fun delete(userWalletIds: List<UserWalletId>): CompletionResult<Unit> = clear()
|
||||
|
||||
override suspend fun clear(): CompletionResult<Unit> = catching {
|
||||
state.update { prevState ->
|
||||
prevState.copy(
|
||||
userWallet = null,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun get(userWalletId: UserWalletId): CompletionResult<UserWallet> = catching {
|
||||
state.value.userWallet ?: walletNotFound()
|
||||
}
|
||||
|
||||
private fun saveInternal(userWallet: UserWallet): CompletionResult<Unit> = catching {
|
||||
state.update { prevState ->
|
||||
prevState.copy(
|
||||
userWallet = userWallet,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun walletNotFound(): Nothing {
|
||||
throw NoSuchElementException("User wallet not found")
|
||||
}
|
||||
|
||||
private data class State(
|
||||
val userWallet: UserWallet? = null,
|
||||
)
|
||||
}
|
||||
|
|
@ -1,39 +0,0 @@
|
|||
package com.tangem.tap.domain.userWalletList.repository.implementation
|
||||
|
||||
import com.tangem.common.core.TangemError
|
||||
import com.tangem.common.core.TangemSdkError
|
||||
import com.tangem.core.analytics.models.Basic
|
||||
import com.tangem.domain.wallets.legacy.UserWalletsListError
|
||||
import com.tangem.utils.converter.Converter
|
||||
|
||||
object BiometricFailReasonConverter : Converter<TangemError, Basic.BiometryFailed.BiometricFailReason> {
|
||||
|
||||
override fun convert(value: TangemError): Basic.BiometryFailed.BiometricFailReason {
|
||||
// 1. Try handle TangemSdkError cases first if error has not been mapped
|
||||
when (value) {
|
||||
is TangemSdkError.AuthenticationCanceled ->
|
||||
return Basic.BiometryFailed.BiometricFailReason.AuthenticationCancelled
|
||||
is TangemSdkError.AuthenticationAlreadyInProgress ->
|
||||
return Basic.BiometryFailed.BiometricFailReason.AuthenticationAlreadyInProgress
|
||||
}
|
||||
|
||||
// 2. For other errors, check if they are of type UserWalletsListError
|
||||
if (value !is UserWalletsListError) {
|
||||
return Basic.BiometryFailed.BiometricFailReason.Other(value.customMessage)
|
||||
}
|
||||
// 3. Map UserWalletsListError to BiometricFailReason
|
||||
return when (value) {
|
||||
UserWalletsListError.AllKeysInvalidated ->
|
||||
Basic.BiometryFailed.BiometricFailReason.AllKeysInvalidated
|
||||
UserWalletsListError.BiometricsAuthenticationDisabled ->
|
||||
Basic.BiometryFailed.BiometricFailReason.BiometricsAuthenticationDisabled
|
||||
is UserWalletsListError.BiometricsAuthenticationLockout ->
|
||||
if (value.isPermanent) {
|
||||
Basic.BiometryFailed.BiometricFailReason.AuthenticationLockoutPermanent
|
||||
} else {
|
||||
Basic.BiometryFailed.BiometricFailReason.AuthenticationLockout
|
||||
}
|
||||
else -> Basic.BiometryFailed.BiometricFailReason.Other(value.customMessage)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,207 +0,0 @@
|
|||
package com.tangem.tap.domain.userWalletList.repository.implementation
|
||||
|
||||
import com.squareup.moshi.JsonAdapter
|
||||
import com.squareup.moshi.Moshi
|
||||
import com.squareup.moshi.Types
|
||||
import com.tangem.common.*
|
||||
import com.tangem.common.authentication.storage.AuthenticatedStorage
|
||||
import com.tangem.common.core.TangemSdkError
|
||||
import com.tangem.common.services.secure.SecureStorage
|
||||
import com.tangem.core.analytics.api.AnalyticsEventHandler
|
||||
import com.tangem.core.analytics.models.AnalyticsParam
|
||||
import com.tangem.core.analytics.models.Basic
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.domain.wallets.legacy.UserWalletsListError
|
||||
import com.tangem.tap.domain.userWalletList.model.UserWalletEncryptionKey
|
||||
import com.tangem.tap.domain.userWalletList.repository.UserWalletsKeysRepository
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
internal class BiometricUserWalletsKeysRepository(
|
||||
moshi: Moshi,
|
||||
private val authenticatedStorage: AuthenticatedStorage,
|
||||
private val secureStorage: SecureStorage,
|
||||
private val analyticsEventHandler: AnalyticsEventHandler,
|
||||
) : UserWalletsKeysRepository {
|
||||
|
||||
private val encryptionKeyAdapter: JsonAdapter<UserWalletEncryptionKey> = moshi.adapter(
|
||||
UserWalletEncryptionKey::class.java,
|
||||
)
|
||||
private val userWalletsIdsListAdapter: JsonAdapter<List<UserWalletId>> = moshi.adapter(
|
||||
Types.newParameterizedType(List::class.java, UserWalletId::class.java),
|
||||
)
|
||||
|
||||
override suspend fun getAll(): CompletionResult<List<UserWalletEncryptionKey>> {
|
||||
return withContext(Dispatchers.IO) {
|
||||
getAllInternal()
|
||||
.mapFailure { error ->
|
||||
val mappedError = when (error) {
|
||||
is TangemSdkError.AuthenticationLockout ->
|
||||
UserWalletsListError.BiometricsAuthenticationLockout(isPermanent = false)
|
||||
is TangemSdkError.AuthenticationPermanentLockout ->
|
||||
UserWalletsListError.BiometricsAuthenticationLockout(isPermanent = true)
|
||||
is TangemSdkError.KeystoreInvalidated ->
|
||||
UserWalletsListError.AllKeysInvalidated
|
||||
is TangemSdkError.AuthenticationUnavailable ->
|
||||
UserWalletsListError.BiometricsAuthenticationDisabled
|
||||
else -> error
|
||||
}
|
||||
analyticsEventHandler.send(
|
||||
Basic.BiometryFailed(
|
||||
source = AnalyticsParam.ScreensSources.SignIn,
|
||||
reason = BiometricFailReasonConverter.convert(mappedError),
|
||||
),
|
||||
)
|
||||
mappedError
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun save(encryptionKey: UserWalletEncryptionKey): CompletionResult<Unit> {
|
||||
return withContext(Dispatchers.IO) {
|
||||
storeEncryptionKey(encryptionKey)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun delete(userWalletsIds: List<UserWalletId>) {
|
||||
return withContext(Dispatchers.IO) {
|
||||
userWalletsIds.forEach { userWalletId ->
|
||||
deleteEncryptionKey(userWalletId)
|
||||
}
|
||||
|
||||
deleteUserWalletsIds(userWalletsIds)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun clear() {
|
||||
return withContext(Dispatchers.IO) {
|
||||
getUserWalletsIds()
|
||||
.forEach { userWalletId ->
|
||||
deleteEncryptionKey(userWalletId)
|
||||
}
|
||||
|
||||
clearUserWalletsIds()
|
||||
}
|
||||
}
|
||||
|
||||
override fun hasSavedEncryptionKeys(): Boolean {
|
||||
return runBlocking {
|
||||
getUserWalletsIds().isNotEmpty()
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun getAllInternal(): CompletionResult<List<UserWalletEncryptionKey>> {
|
||||
return catching {
|
||||
val userWalletIds = getUserWalletsIds()
|
||||
|
||||
getEncryptionKeys(userWalletIds)
|
||||
}
|
||||
.doOnFailure { error ->
|
||||
when (error) {
|
||||
is TangemSdkError.KeystoreInvalidated -> {
|
||||
getUserWalletsIds().forEach { userWalletId ->
|
||||
deleteEncryptionKey(userWalletId)
|
||||
}
|
||||
}
|
||||
else -> Unit
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun getEncryptionKeys(userWalletsIds: List<UserWalletId>): List<UserWalletEncryptionKey> {
|
||||
val keys = userWalletsIds.map { userWalletId ->
|
||||
StorageKey.UserWalletEncryptionKey(userWalletId).name
|
||||
}
|
||||
|
||||
return authenticatedStorage.get(keys).mapNotNull { (_, encodedData) ->
|
||||
encodedData.decodeToKey()
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun storeEncryptionKey(encryptionKey: UserWalletEncryptionKey): CompletionResult<Unit> {
|
||||
return catching {
|
||||
authenticatedStorage.store(
|
||||
keyAlias = StorageKey.UserWalletEncryptionKey(encryptionKey.walletId).name,
|
||||
data = encryptionKey.encode(),
|
||||
)
|
||||
}
|
||||
.map { storeUserWalletId(encryptionKey.walletId) }
|
||||
}
|
||||
|
||||
private fun deleteEncryptionKey(userWalletId: UserWalletId) {
|
||||
authenticatedStorage.delete(StorageKey.UserWalletEncryptionKey(userWalletId).name)
|
||||
}
|
||||
|
||||
private suspend fun getUserWalletsIds(): List<UserWalletId> {
|
||||
return withContext(Dispatchers.IO) {
|
||||
secureStorage.get(StorageKey.UserWalletIds.name)
|
||||
.decodeToUserWalletsIds()
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun storeUserWalletId(userWalletId: UserWalletId) {
|
||||
val userWalletIds = (getUserWalletsIds() + userWalletId).distinct()
|
||||
|
||||
withContext(Dispatchers.IO) {
|
||||
secureStorage.store(userWalletIds.encode(), StorageKey.UserWalletIds.name)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun deleteUserWalletsIds(userWalletsIds: List<UserWalletId>) {
|
||||
val remainingIds = getUserWalletsIds() - userWalletsIds.toSet()
|
||||
|
||||
withContext(Dispatchers.IO) {
|
||||
secureStorage.store(remainingIds.encode(), StorageKey.UserWalletIds.name)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun clearUserWalletsIds() {
|
||||
withContext(Dispatchers.IO) {
|
||||
secureStorage.delete(StorageKey.UserWalletIds.name)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun UserWalletEncryptionKey.encode(): ByteArray {
|
||||
return withContext(Dispatchers.Default) {
|
||||
encryptionKeyAdapter.toJson(this@encode)
|
||||
.encodeToByteArray(throwOnInvalidSequence = true)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun ByteArray?.decodeToKey(): UserWalletEncryptionKey? {
|
||||
return withContext(Dispatchers.Default) {
|
||||
this@decodeToKey
|
||||
?.decodeToString(throwOnInvalidSequence = true)
|
||||
?.let(encryptionKeyAdapter::fromJson)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun List<UserWalletId>.encode(): ByteArray {
|
||||
return withContext(Dispatchers.Default) {
|
||||
userWalletsIdsListAdapter.toJson(this@encode)
|
||||
.encodeToByteArray(throwOnInvalidSequence = true)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun ByteArray?.decodeToUserWalletsIds(): List<UserWalletId> {
|
||||
return withContext(Dispatchers.Default) {
|
||||
this@decodeToUserWalletsIds
|
||||
?.decodeToString(throwOnInvalidSequence = true)
|
||||
?.let(userWalletsIdsListAdapter::fromJson)
|
||||
.orEmpty()
|
||||
}
|
||||
}
|
||||
|
||||
private sealed interface StorageKey {
|
||||
val name: String
|
||||
|
||||
class UserWalletEncryptionKey(userWalletId: UserWalletId) : StorageKey {
|
||||
override val name: String = "user_wallet_encryption_key_${userWalletId.stringValue}"
|
||||
}
|
||||
|
||||
object UserWalletIds : StorageKey {
|
||||
override val name: String = "user_wallets_ids_with_saved_keys"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -104,8 +104,6 @@ internal fun List<UserWallet>.updateWith(
|
|||
}
|
||||
}
|
||||
|
||||
internal fun List<UserWallet>.lockAll(): List<UserWallet> = map(UserWallet::lock)
|
||||
|
||||
internal fun UserWallet.lock(): UserWallet = when (this) {
|
||||
is UserWallet.Cold -> {
|
||||
copy(
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue