Updated on 2026-08-14

This commit is contained in:
Tangem 2026-02-10 04:50:44 +03:00
parent f89ab30e5a
commit 64fda96648
9 changed files with 56 additions and 23 deletions

View file

@ -7,6 +7,7 @@ import com.tangem.domain.balancehiding.UpdateBalanceHidingSettingsUseCase
import com.tangem.domain.balancehiding.repositories.BalanceHidingRepository
import com.tangem.domain.settings.*
import com.tangem.domain.settings.repositories.AppRatingRepository
import com.tangem.domain.settings.repositories.LegacySettingsRepository
import com.tangem.domain.settings.repositories.PermissionRepository
import com.tangem.domain.settings.repositories.SettingsRepository
import com.tangem.domain.settings.usercountry.FetchUserCountryUseCase
@ -68,10 +69,14 @@ internal object SettingsDomainModule {
@Provides
@Singleton
fun providesCanUseBiometryUseCase(tangemSdkManager: TangemSdkManager): CanUseBiometryUseCase {
return CanUseBiometryUseCase(
legacySettingsRepository = DefaultLegacySettingsRepository(tangemSdkManager = tangemSdkManager),
)
fun provideLegacySettingsRepository(tangemSdkManager: TangemSdkManager): LegacySettingsRepository {
return DefaultLegacySettingsRepository(tangemSdkManager = tangemSdkManager)
}
@Provides
@Singleton
fun providesCanUseBiometryUseCase(legacySettingsRepository: LegacySettingsRepository): CanUseBiometryUseCase {
return CanUseBiometryUseCase(legacySettingsRepository = legacySettingsRepository)
}
@Provides

View file

@ -8,4 +8,8 @@ internal class DefaultLegacySettingsRepository(
) : LegacySettingsRepository {
override suspend fun canUseBiometry(): Boolean = tangemSdkManager.checkCanUseBiometry()
override suspend fun canUseBiometryStrict(): Boolean {
return tangemSdkManager.checkCanUseBiometry() && tangemSdkManager.checkNeedEnrollBiometrics().not()
}
}

View file

@ -31,6 +31,7 @@ dependencies {
implementation(projects.domain.tokens.models)
implementation(projects.domain.wallets)
implementation(projects.domain.wallets.models)
implementation(projects.domain.settings)
/** DI */
implementation(deps.hilt.android)

View file

@ -4,6 +4,7 @@ import com.tangem.common.core.TangemSdkError
import com.tangem.domain.common.wallets.UserWalletsListRepository
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.domain.settings.repositories.LegacySettingsRepository
import com.tangem.domain.wallets.hot.HotWalletAccessor
import com.tangem.domain.wallets.hot.HotWalletPasswordRequester
import com.tangem.domain.wallets.repository.WalletsRepository
@ -17,18 +18,21 @@ import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.launch
import java.util.concurrent.ConcurrentHashMap
import javax.inject.Inject
import javax.inject.Singleton
@Singleton
class DefaultHotWalletAccessor @Inject constructor(
private val tangemHotSdk: TangemHotSdk,
private val userWalletsListRepository: UserWalletsListRepository,
private val hotWalletPasswordRequester: HotWalletPasswordRequester,
private val walletsRepository: WalletsRepository,
private val legacySettingsRepository: LegacySettingsRepository,
dispatchers: CoroutineDispatcherProvider,
) : HotWalletAccessor {
private val scope = CoroutineScope(context = SupervisorJob() + dispatchers.io)
private var contextualUnlockHotWallet: ConcurrentHashMap<HotWalletId, UnlockHotWallet?> = ConcurrentHashMap()
private val contextualUnlockHotWallet: ConcurrentHashMap<HotWalletId, UnlockHotWallet?> = ConcurrentHashMap()
override suspend fun signHashes(hotWalletId: HotWalletId, dataToSign: List<DataToSign>): List<SignedData> =
hotSdkRequest(hotWalletId) { unlock ->
@ -82,7 +86,7 @@ class DefaultHotWalletAccessor @Inject constructor(
}
private suspend fun <T> hotSdkRequest(hotWalletId: HotWalletId, block: suspend (unlock: UnlockHotWallet) -> T): T {
val isAccessCodeRequired = walletsRepository.requireAccessCode()
val isAccessCodeRequired = isAccessCodeRequired()
val auth = when (hotWalletId.authType) {
HotWalletId.AuthType.NoPassword -> HotAuth.NoAuth
@ -134,7 +138,7 @@ class DefaultHotWalletAccessor @Inject constructor(
}
private suspend fun updateBiometryAuthIfNeeded(hotWalletId: HotWalletId, originalAuth: HotAuth) {
val isAccessCodeRequired = walletsRepository.requireAccessCode()
val isAccessCodeRequired = isAccessCodeRequired()
val isUseBiometricAuthenticationEnabled = walletsRepository.useBiometricAuthentication()
if (originalAuth is HotAuth.Password && isUseBiometricAuthenticationEnabled && isAccessCodeRequired.not()) {
@ -167,7 +171,7 @@ class DefaultHotWalletAccessor @Inject constructor(
): T = runSuspendCatching {
block(auth)
}.getOrElse { exception ->
if (auth is HotAuth.Biometry && exception.isBiometryError()) {
if (auth is HotAuth.Biometry && (exception.isBiometryError() || exception.isBiometryReset())) {
val shouldRetryBiometry = exception is TangemSdkError.AuthenticationCanceled
// fallback to password if biometry fails
@ -215,6 +219,14 @@ class DefaultHotWalletAccessor @Inject constructor(
?: throw TangemSdkError.UserCancelled()
}
private suspend fun isAccessCodeRequired(): Boolean {
return walletsRepository.requireAccessCode() || legacySettingsRepository.canUseBiometryStrict().not()
}
private fun Throwable.isBiometryReset(): Boolean {
return this is IllegalStateException
}
private fun Throwable.isBiometryError(): Boolean {
return this is TangemSdkError.AuthenticationFailed ||
this is TangemSdkError.AuthenticationCanceled ||

View file

@ -4,5 +4,8 @@ import com.tangem.domain.settings.repositories.LegacySettingsRepository
class CanUseBiometryUseCase(private val legacySettingsRepository: LegacySettingsRepository) {
@Deprecated("You probably want to use strict() instead. Check implementation", ReplaceWith("strict()"))
suspend operator fun invoke(): Boolean = legacySettingsRepository.canUseBiometry()
suspend fun strict(): Boolean = legacySettingsRepository.canUseBiometryStrict()
}

View file

@ -3,4 +3,6 @@ package com.tangem.domain.settings.repositories
interface LegacySettingsRepository {
suspend fun canUseBiometry(): Boolean
suspend fun canUseBiometryStrict(): Boolean
}

View file

@ -31,6 +31,8 @@ import com.tangem.hot.sdk.TangemHotSdk
import com.tangem.hot.sdk.model.HotAuth
import com.tangem.hot.sdk.model.HotWalletId
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import com.tangem.utils.coroutines.JobHolder
import com.tangem.utils.coroutines.saveIn
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
@ -63,6 +65,8 @@ internal class AccessCodeModel @Inject constructor(
private val params = paramsContainer.require<AccessCodeComponent.Params>()
private val settingCodeJobHolder = JobHolder()
internal val uiState: StateFlow<AccessCodeUM>
field = MutableStateFlow(getInitialState())
@ -197,23 +201,25 @@ internal class AccessCodeModel @Inject constructor(
}
}
private fun setCode(userWalletId: UserWalletId, accessCode: String) {
private suspend fun setCode(userWalletId: UserWalletId, accessCode: String) = coroutineScope {
if (settingCodeJobHolder.isActive) {
return@coroutineScope
}
params.callbacks.onAccessCodeUpdateStarted(params.userWalletId)
modelScope.launch {
val userWallet = getUserWalletUseCase(userWalletId)
.getOrElse { error("User wallet with id $userWalletId not found") }
.requireHotWallet()
val userWallet = getUserWalletUseCase(userWalletId)
.getOrElse { error("User wallet with id $userWalletId not found") }
.requireHotWallet()
tryToAskForBiometry()
tryToAskForBiometry()
val settingCodeJob = launch(dispatchers.main) {
setCodeOperation(userWallet, accessCode)
params.callbacks.onAccessCodeUpdated(params.userWalletId)
}
val settingCodeJob = launch(dispatchers.main) {
setCodeOperation(userWallet, accessCode)
params.callbacks.onAccessCodeUpdated(params.userWalletId)
}.saveIn(settingCodeJobHolder)
setLoadingIfLongJob(settingCodeJob)
}
setLoadingIfLongJob(settingCodeJob)
}
/**
@ -246,7 +252,7 @@ internal class AccessCodeModel @Inject constructor(
lockMethod = UserWalletsListRepository.LockMethod.AccessCode(accessCode.toCharArray()),
)
if (walletsRepository.requireAccessCode().not() && canUseBiometryUseCase()) {
if (walletsRepository.requireAccessCode().not() && canUseBiometryUseCase.strict()) {
val newHotWalletIdWithBiometry = tangemHotSdk.changeAuth(
unlockHotWallet = unlockHotWallet,
auth = HotAuth.Biometry,

View file

@ -219,7 +219,7 @@ internal class HotAccessCodeRequestModel @Inject constructor(
}
private suspend fun HotWalletPasswordRequester.AttemptRequest.isBiometryButtonVisible(): Boolean =
hasBiometry && canUseBiometryUseCase()
hasBiometry && canUseBiometryUseCase.strict()
private fun dismissState() {
uiState.update {

View file

@ -254,7 +254,7 @@ internal class WelcomeModel @Inject constructor(
}
private suspend fun canUnlockWithBiometrics(): Boolean {
return canUseBiometryUseCase() && walletsRepository.useBiometricAuthentication()
return canUseBiometryUseCase.strict() && walletsRepository.useBiometricAuthentication()
}
suspend fun nonBiometricUnlockWallet(userWalletId: UserWalletId) {