diff --git a/app/src/main/java/com/tangem/tap/di/domain/SettingsDomainModule.kt b/app/src/main/java/com/tangem/tap/di/domain/SettingsDomainModule.kt index 2cc898457b..ce2088f11c 100644 --- a/app/src/main/java/com/tangem/tap/di/domain/SettingsDomainModule.kt +++ b/app/src/main/java/com/tangem/tap/di/domain/SettingsDomainModule.kt @@ -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 diff --git a/app/src/main/java/com/tangem/tap/domain/settings/DefaultLegacySettingsRepository.kt b/app/src/main/java/com/tangem/tap/domain/settings/DefaultLegacySettingsRepository.kt index e074ddc8f8..b075775429 100644 --- a/app/src/main/java/com/tangem/tap/domain/settings/DefaultLegacySettingsRepository.kt +++ b/app/src/main/java/com/tangem/tap/domain/settings/DefaultLegacySettingsRepository.kt @@ -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() + } } \ No newline at end of file diff --git a/data/wallets/build.gradle.kts b/data/wallets/build.gradle.kts index 5949dd0174..aafa1080db 100644 --- a/data/wallets/build.gradle.kts +++ b/data/wallets/build.gradle.kts @@ -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) diff --git a/data/wallets/src/main/java/com/tangem/data/wallets/hot/DefaultHotWalletAccessor.kt b/data/wallets/src/main/java/com/tangem/data/wallets/hot/DefaultHotWalletAccessor.kt index f829c76c6f..62668755ef 100644 --- a/data/wallets/src/main/java/com/tangem/data/wallets/hot/DefaultHotWalletAccessor.kt +++ b/data/wallets/src/main/java/com/tangem/data/wallets/hot/DefaultHotWalletAccessor.kt @@ -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 = ConcurrentHashMap() + private val contextualUnlockHotWallet: ConcurrentHashMap = ConcurrentHashMap() override suspend fun signHashes(hotWalletId: HotWalletId, dataToSign: List): List = hotSdkRequest(hotWalletId) { unlock -> @@ -82,7 +86,7 @@ class DefaultHotWalletAccessor @Inject constructor( } private suspend fun 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 || diff --git a/domain/settings/src/main/java/com/tangem/domain/settings/CanUseBiometryUseCase.kt b/domain/settings/src/main/java/com/tangem/domain/settings/CanUseBiometryUseCase.kt index 880f941db8..c84d1f6361 100644 --- a/domain/settings/src/main/java/com/tangem/domain/settings/CanUseBiometryUseCase.kt +++ b/domain/settings/src/main/java/com/tangem/domain/settings/CanUseBiometryUseCase.kt @@ -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() } \ No newline at end of file diff --git a/domain/settings/src/main/java/com/tangem/domain/settings/repositories/LegacySettingsRepository.kt b/domain/settings/src/main/java/com/tangem/domain/settings/repositories/LegacySettingsRepository.kt index e1f1b8ebb7..f37a051b33 100644 --- a/domain/settings/src/main/java/com/tangem/domain/settings/repositories/LegacySettingsRepository.kt +++ b/domain/settings/src/main/java/com/tangem/domain/settings/repositories/LegacySettingsRepository.kt @@ -3,4 +3,6 @@ package com.tangem.domain.settings.repositories interface LegacySettingsRepository { suspend fun canUseBiometry(): Boolean + + suspend fun canUseBiometryStrict(): Boolean } \ No newline at end of file diff --git a/features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/accesscode/AccessCodeModel.kt b/features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/accesscode/AccessCodeModel.kt index 70ac01eac8..9b046ff611 100644 --- a/features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/accesscode/AccessCodeModel.kt +++ b/features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/accesscode/AccessCodeModel.kt @@ -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() + private val settingCodeJobHolder = JobHolder() + internal val uiState: StateFlow 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, diff --git a/features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/accesscoderequest/HotAccessCodeRequestModel.kt b/features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/accesscoderequest/HotAccessCodeRequestModel.kt index ba2bbf512a..1a2cb122b6 100644 --- a/features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/accesscoderequest/HotAccessCodeRequestModel.kt +++ b/features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/accesscoderequest/HotAccessCodeRequestModel.kt @@ -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 { diff --git a/features/welcome/impl/src/main/kotlin/com/tangem/features/welcome/impl/model/WelcomeModel.kt b/features/welcome/impl/src/main/kotlin/com/tangem/features/welcome/impl/model/WelcomeModel.kt index 83ab025490..bc5b6dc548 100644 --- a/features/welcome/impl/src/main/kotlin/com/tangem/features/welcome/impl/model/WelcomeModel.kt +++ b/features/welcome/impl/src/main/kotlin/com/tangem/features/welcome/impl/model/WelcomeModel.kt @@ -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) {