Updated on 2026-08-14
This commit is contained in:
parent
411a591509
commit
94a4486443
28 changed files with 393 additions and 178 deletions
|
|
@ -54,8 +54,8 @@ internal object SettingsDomainModule {
|
|||
@Singleton
|
||||
fun providesShouldShowSaveWalletScreenUseCase(
|
||||
settingsRepository: SettingsRepository,
|
||||
): ShouldShowSaveWalletScreenUseCase {
|
||||
return ShouldShowSaveWalletScreenUseCase(settingsRepository = settingsRepository)
|
||||
): ShouldShowAskBiometryUseCase {
|
||||
return ShouldShowAskBiometryUseCase(settingsRepository = settingsRepository)
|
||||
}
|
||||
|
||||
@Provides
|
||||
|
|
@ -138,10 +138,8 @@ internal object SettingsDomainModule {
|
|||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideSetSaveWalletScreenShownUseCase(
|
||||
settingsRepository: SettingsRepository,
|
||||
): SetSaveWalletScreenShownUseCase {
|
||||
return SetSaveWalletScreenShownUseCase(settingsRepository = settingsRepository)
|
||||
fun provideSetSaveWalletScreenShownUseCase(settingsRepository: SettingsRepository): SetAskBiometryShownUseCase {
|
||||
return SetAskBiometryShownUseCase(settingsRepository = settingsRepository)
|
||||
}
|
||||
|
||||
@Provides
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import com.tangem.common.json.TangemSdkAdapter
|
|||
import com.tangem.common.services.secure.SecureStorage
|
||||
import com.tangem.core.analytics.api.AnalyticsEventHandler
|
||||
import com.tangem.datasource.local.preferences.AppPreferencesStore
|
||||
import com.tangem.domain.card.ScanCardProcessor
|
||||
import com.tangem.domain.models.scan.serialization.*
|
||||
import com.tangem.domain.visa.model.VisaActivationRemoteState
|
||||
import com.tangem.domain.visa.model.VisaCardActivationStatus
|
||||
|
|
@ -122,6 +123,7 @@ internal object UserWalletsListManagerModule {
|
|||
passwordRequester: HotWalletPasswordRequester,
|
||||
appPreferencesStore: AppPreferencesStore,
|
||||
hotWalletAccessCodeAttemptsRepository: HotWalletAccessCodeAttemptsRepository,
|
||||
scanCardProcessor: ScanCardProcessor,
|
||||
): UserWalletsListRepository {
|
||||
val moshi = buildMoshi()
|
||||
val secureStorage = buildSecureStorage(applicationContext = applicationContext)
|
||||
|
|
@ -168,6 +170,7 @@ internal object UserWalletsListManagerModule {
|
|||
appPreferencesStore = appPreferencesStore,
|
||||
savePersistentInformation = ProviderSuspend { true }, // Always save persistent information for now
|
||||
hotWalletAccessCodeAttemptsRepository = hotWalletAccessCodeAttemptsRepository,
|
||||
scanCardProcessor = scanCardProcessor,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import com.tangem.common.map
|
|||
import com.tangem.datasource.local.preferences.AppPreferencesStore
|
||||
import com.tangem.datasource.local.preferences.PreferencesKeys
|
||||
import com.tangem.datasource.local.preferences.utils.getSyncOrDefault
|
||||
import com.tangem.domain.card.ScanCardProcessor
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.domain.models.wallet.isLocked
|
||||
|
|
@ -38,6 +39,9 @@ import com.tangem.utils.ProviderSuspend
|
|||
import com.tangem.utils.extensions.indexOfFirstOrNull
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.update
|
||||
import com.tangem.core.analytics.models.AnalyticsParam
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
|
||||
@Suppress("LongParameterList", "LargeClass")
|
||||
internal class DefaultUserWalletsListRepository(
|
||||
|
|
@ -50,36 +54,45 @@ internal class DefaultUserWalletsListRepository(
|
|||
private val savePersistentInformation: ProviderSuspend<Boolean>,
|
||||
private val appPreferencesStore: AppPreferencesStore,
|
||||
private val hotWalletAccessCodeAttemptsRepository: HotWalletAccessCodeAttemptsRepository,
|
||||
private val scanCardProcessor: ScanCardProcessor,
|
||||
) : UserWalletsListRepository {
|
||||
|
||||
override val userWallets = MutableStateFlow<List<UserWallet>?>(null)
|
||||
override val selectedUserWallet = MutableStateFlow<UserWallet?>(null)
|
||||
private val mutex = Mutex()
|
||||
|
||||
override suspend fun load() {
|
||||
if (userWallets.value != null) return
|
||||
mutex.withLock {
|
||||
if (userWallets.value != null) return
|
||||
|
||||
if (savePersistentInformation().not()) {
|
||||
// If we don't save persistent information, we don't need to load user wallets
|
||||
// and we should clear any existing data
|
||||
clearPersistentData()
|
||||
updateWallets { emptyList() }
|
||||
return
|
||||
}
|
||||
|
||||
val unsecuredEncryptionKeys = userWalletEncryptionKeysRepository.getAllUnsecured()
|
||||
|
||||
publicInformationRepository.getAll()
|
||||
.map { it.toUserWallets() }
|
||||
.flatMap { wallets ->
|
||||
sensitiveInformationRepository.getAll(unsecuredEncryptionKeys)
|
||||
.map { wallets.updateWith(it) }
|
||||
}.doOnSuccess {
|
||||
userWallets.value = it
|
||||
if (savePersistentInformation().not()) {
|
||||
// If we don't save persistent information, we don't need to load user wallets
|
||||
// and we should clear any existing data
|
||||
clearPersistentData()
|
||||
updateWallets { emptyList() }
|
||||
return
|
||||
}
|
||||
|
||||
val selectedUserWalletId = selectedUserWalletRepository.get()
|
||||
selectedUserWallet.value = userWallets.value?.firstOrNull { it.walletId == selectedUserWalletId }
|
||||
?: userWallets.value?.firstOrNull()
|
||||
val unsecuredEncryptionKeys = userWalletEncryptionKeysRepository.getAllUnsecured()
|
||||
|
||||
publicInformationRepository.getAll()
|
||||
.map { it.toUserWallets() }
|
||||
.flatMap { wallets ->
|
||||
sensitiveInformationRepository.getAll(unsecuredEncryptionKeys)
|
||||
.map { wallets.updateWith(it) }
|
||||
}
|
||||
.doOnSuccess { loadedWallets ->
|
||||
userWallets.update { toUpdate ->
|
||||
val selectedUserWalletId = selectedUserWalletRepository.get()
|
||||
selectedUserWallet.value = loadedWallets.firstOrNull { it.walletId == selectedUserWalletId }
|
||||
?: loadedWallets.firstOrNull()?.also {
|
||||
selectedUserWalletRepository.set(it.walletId)
|
||||
}
|
||||
|
||||
loadedWallets
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun userWalletsSync(): List<UserWallet> {
|
||||
|
|
@ -191,18 +204,17 @@ internal class DefaultUserWalletsListRepository(
|
|||
|
||||
userWalletEncryptionKeysRepository.delete(userWalletIds)
|
||||
|
||||
val userWalletsBeforeDelete = userWallets.value ?: return@either
|
||||
|
||||
userWallets.update { currentWallets ->
|
||||
currentWallets?.filterNot { it.walletId in userWalletIds }
|
||||
}
|
||||
|
||||
selectedUserWallet.update { currentSelected ->
|
||||
if (currentSelected == null) return@update null
|
||||
|
||||
userWallets.value?.findAvailableUserWallet(
|
||||
userWalletsBeforeDelete.indexOfFirstOrNull { it.walletId == currentSelected.walletId } ?: 0,
|
||||
)
|
||||
val updatedWallets = currentWallets?.filter { userWalletIds.contains(it.walletId).not() }
|
||||
selectedUserWallet.update { currentSelected ->
|
||||
if (currentSelected == null) return@update null
|
||||
val newSelected = updatedWallets?.findAvailableUserWallet(
|
||||
currentWallets.indexOfFirstOrNull { it.walletId == currentSelected.walletId } ?: 0,
|
||||
)
|
||||
selectedUserWalletRepository.set(newSelected?.walletId)
|
||||
newSelected
|
||||
}
|
||||
updatedWallets
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -258,7 +270,7 @@ internal class DefaultUserWalletsListRepository(
|
|||
raise(UnlockWalletError.UnableToUnlock)
|
||||
}
|
||||
|
||||
tangemSdkManagerProvider().scanProduct()
|
||||
scanCardProcessor.scan(analyticsSource = AnalyticsParam.ScreensSources.SignIn)
|
||||
.doOnSuccess { scanResponse ->
|
||||
val expectedId = UserWalletIdBuilder.scanResponse(scanResponse).build()
|
||||
|
||||
|
|
@ -266,9 +278,14 @@ internal class DefaultUserWalletsListRepository(
|
|||
raise(UnlockWalletError.ScannedCardWalletNotMatched)
|
||||
}
|
||||
|
||||
saveWithoutLock(userWallet.copy(scanResponse = scanResponse), canOverride = true)
|
||||
.mapLeft { UnlockWalletError.UnableToUnlock }
|
||||
.bind()
|
||||
val encryptionKey = UserWalletEncryptionKey(
|
||||
walletId = userWallet.walletId,
|
||||
encryptionKey = scanResponse.encryptionKey ?: raise(UnlockWalletError.UnableToUnlock),
|
||||
)
|
||||
|
||||
sensitiveInformationRepository.getAll(listOf(encryptionKey))
|
||||
.doOnSuccess { sensitiveInfo -> updateWallets { it?.updateWith(sensitiveInfo) } }
|
||||
.doOnFailure { error -> raise(UnlockWalletError.UnableToUnlock) }
|
||||
}
|
||||
.doOnFailure {
|
||||
raise(UnlockWalletError.UserCancelled)
|
||||
|
|
@ -278,7 +295,8 @@ internal class DefaultUserWalletsListRepository(
|
|||
}
|
||||
|
||||
override suspend fun unlockAllWallets(): Either<UnlockWalletError, Unit> = either {
|
||||
val userWalletIds = userWalletsSync().map { it.walletId }.toSet()
|
||||
val userWallets = userWalletsSync()
|
||||
val userWalletIds = userWallets.map { it.walletId }.toSet()
|
||||
val biometricKeys = runCatching {
|
||||
userWalletEncryptionKeysRepository.getAllBiometric()
|
||||
}.getOrElse {
|
||||
|
|
@ -291,7 +309,7 @@ internal class DefaultUserWalletsListRepository(
|
|||
val unlockedWalletsIds = allKeys.map { it.walletId }
|
||||
|
||||
val unlockedWallets = unlockedWalletsIds.mapNotNull { id ->
|
||||
userWalletsSync().firstOrNull { it.walletId == id }
|
||||
userWallets.firstOrNull { it.walletId == id }
|
||||
}
|
||||
|
||||
// Remove all password attempts for unlocked hot wallets
|
||||
|
|
@ -306,7 +324,7 @@ internal class DefaultUserWalletsListRepository(
|
|||
|
||||
sensitiveInformationRepository.getAll(allKeys)
|
||||
.doOnSuccess { sensitiveInfo ->
|
||||
updateWallets { it?.updateWith(sensitiveInfo) }
|
||||
updateWallets { userWallets.updateWith(sensitiveInfo) }
|
||||
}
|
||||
.doOnFailure { raise(UnlockWalletError.UnableToUnlock) }
|
||||
}
|
||||
|
|
@ -390,11 +408,13 @@ internal class DefaultUserWalletsListRepository(
|
|||
}
|
||||
|
||||
private fun updateWallets(block: (List<UserWallet>?) -> List<UserWallet>?) {
|
||||
userWallets.update(block)
|
||||
|
||||
selectedUserWallet.update { currentSelected ->
|
||||
if (currentSelected == null) return@update null
|
||||
userWallets.value?.find { it.walletId == currentSelected.walletId }
|
||||
userWallets.update {
|
||||
val updated = block(it)
|
||||
selectedUserWallet.update { currentSelected ->
|
||||
if (currentSelected == null) return@update null
|
||||
updated?.find { it.walletId == currentSelected.walletId }
|
||||
}
|
||||
updated
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import com.tangem.common.extensions.calculateSha256
|
|||
import com.tangem.domain.common.extensions.calculateHmacSha256
|
||||
import com.tangem.domain.models.MobileWallet
|
||||
import com.tangem.domain.models.scan.CardDTO
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
|
||||
internal val UserWallet.encryptionKey: ByteArray?
|
||||
|
|
@ -19,6 +20,9 @@ private fun calculateEncryptionKey(publicKey: ByteArray): ByteArray {
|
|||
return message.calculateHmacSha256(keyHash)
|
||||
}
|
||||
|
||||
val ScanResponse.encryptionKey: ByteArray?
|
||||
get() = findPublicKey(this.card.wallets)?.let { calculateEncryptionKey(it) }
|
||||
|
||||
private fun findPublicKey(wallets: List<CardDTO.Wallet>): ByteArray? {
|
||||
return wallets.firstOrNull()?.publicKey
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue