From 86d335db40ddb82606640f92c2778302c70a9145 Mon Sep 17 00:00:00 2001 From: danvleju-rdx <163979791+danvleju-rdx@users.noreply.github.com> Date: Mon, 9 Sep 2024 18:06:28 +0300 Subject: [PATCH] Biometrics check updates (#1326) --- .../UserDefaults+Dependency+Extension.swift | 9 +++ .../DebugUserDefaultsContents.swift | 2 + .../Preferences/Preferences.swift | 25 +++----- .../Features/SplashFeature/Splash.swift | 57 ++++++++++++++----- 4 files changed, 63 insertions(+), 30 deletions(-) diff --git a/RadixWallet/Clients/UserDefaults+Dependency+Extension/UserDefaults+Dependency+Extension.swift b/RadixWallet/Clients/UserDefaults+Dependency+Extension/UserDefaults+Dependency+Extension.swift index 1e5cc7d199..305e4e471e 100644 --- a/RadixWallet/Clients/UserDefaults+Dependency+Extension/UserDefaults+Dependency+Extension.swift +++ b/RadixWallet/Clients/UserDefaults+Dependency+Extension/UserDefaults+Dependency+Extension.swift @@ -16,6 +16,7 @@ public enum UserDefaultsKey: String, Sendable, Hashable, CaseIterable { case showRelinkConnectorsAfterUpdate case showRelinkConnectorsAfterProfileRestore case homeCards + case appLockMessageShown /// DO NOT CHANGE THIS KEY case activeProfileID @@ -233,6 +234,14 @@ extension UserDefaults.Dependency { public func setHomeCards(_ value: Data) { set(data: value, key: .homeCards) } + + public var appLockMessageShown: Bool { + bool(key: .appLockMessageShown) + } + + public func setAppLockMessageShown(_ value: Bool) { + set(value, forKey: Key.appLockMessageShown.rawValue) + } } // MARK: - BackupResult diff --git a/RadixWallet/Features/SettingsFeature/DebugSettings/Children/DebugUserDefaultsContents/DebugUserDefaultsContents.swift b/RadixWallet/Features/SettingsFeature/DebugSettings/Children/DebugUserDefaultsContents/DebugUserDefaultsContents.swift index 6a7c506ace..565460120e 100644 --- a/RadixWallet/Features/SettingsFeature/DebugSettings/Children/DebugUserDefaultsContents/DebugUserDefaultsContents.swift +++ b/RadixWallet/Features/SettingsFeature/DebugSettings/Children/DebugUserDefaultsContents/DebugUserDefaultsContents.swift @@ -120,6 +120,8 @@ extension UserDefaults.Dependency.Key { return [userDefaults.showRelinkConnectorsAfterProfileRestore].map(String.init(describing:)) case .homeCards: return [userDefaults.getHomeCards() == nil ? "No Data available" : "Data available"] + case .appLockMessageShown: + return [userDefaults.appLockMessageShown].map(String.init(describing:)) } } } diff --git a/RadixWallet/Features/SettingsFeature/Preferences/Preferences.swift b/RadixWallet/Features/SettingsFeature/Preferences/Preferences.swift index c73cc84073..a097f776f9 100644 --- a/RadixWallet/Features/SettingsFeature/Preferences/Preferences.swift +++ b/RadixWallet/Features/SettingsFeature/Preferences/Preferences.swift @@ -25,7 +25,7 @@ public struct Preferences: Sendable, FeatureReducer { public enum InternalAction: Sendable, Equatable { case loadedAppPreferences(AppPreferences) - case advancedLockDisableResult(TaskResult) + case advancedLockToggleResult(authResult: TaskResult, isEnabled: Bool) } public struct Destination: DestinationReducer { @@ -111,10 +111,11 @@ public struct Preferences: Sendable, FeatureReducer { } case let .advancedLockToogled(isEnabled): - if !isEnabled { - return confirmAdvancedLockDisableWithBiometrics() - } else { - return updateAdvancedLock(state: &state, isEnabled: isEnabled) + return .run { send in + let authResult = await TaskResult { + try await localAuthenticationClient.authenticateWithBiometrics() + } + await send(.internal(.advancedLockToggleResult(authResult: authResult, isEnabled: isEnabled))) } case .exportLogsButtonTapped: @@ -133,13 +134,13 @@ public struct Preferences: Sendable, FeatureReducer { state.appPreferences = appPreferences return .none - case let .advancedLockDisableResult(.failure(error)): + case let .advancedLockToggleResult(.failure(error), _): errorQueue.schedule(error) return .none - case let .advancedLockDisableResult(.success(success)): + case let .advancedLockToggleResult(.success(success), isEnabled): guard success else { return .none } - return updateAdvancedLock(state: &state, isEnabled: false) + return updateAdvancedLock(state: &state, isEnabled: isEnabled) } } @@ -158,14 +159,6 @@ public struct Preferences: Sendable, FeatureReducer { } } - private func confirmAdvancedLockDisableWithBiometrics() -> Effect { - .run { send in - await send(.internal(.advancedLockDisableResult(.init { - try await localAuthenticationClient.authenticateWithBiometrics() - }))) - } - } - private func updateAdvancedLock(state: inout State, isEnabled: Bool) -> Effect { state.appPreferences?.security.isAdvancedLockEnabled = isEnabled guard let preferences = state.appPreferences else { return .none } diff --git a/RadixWallet/Features/SplashFeature/Splash.swift b/RadixWallet/Features/SplashFeature/Splash.swift index 92949df9f1..317326675a 100644 --- a/RadixWallet/Features/SplashFeature/Splash.swift +++ b/RadixWallet/Features/SplashFeature/Splash.swift @@ -34,6 +34,7 @@ public struct Splash: Sendable, FeatureReducer { case passcodeConfigResult(TaskResult) case biometricsCheckResult(TaskResult) case advancedLockStateLoaded(isEnabled: Bool) + case showAppLockMessage } public enum DelegateAction: Sendable, Equatable { @@ -53,6 +54,7 @@ public struct Splash: Sendable, FeatureReducer { public enum ErrorAlert: Sendable, Equatable { case retryVerifyPasscodeButtonTapped case openSettingsButtonTapped + case appLockOkButtonTapped } } @@ -64,6 +66,7 @@ public struct Splash: Sendable, FeatureReducer { @Dependency(\.localAuthenticationClient) var localAuthenticationClient @Dependency(\.onboardingClient) var onboardingClient @Dependency(\.openURL) var openURL + @Dependency(\.userDefaults) var userDefaults public init() {} @@ -82,14 +85,24 @@ public struct Splash: Sendable, FeatureReducer { return .run { send in let isAdvancedLockEnabled = await onboardingClient.loadProfile().appPreferences.security.isAdvancedLockEnabled + guard #available(iOS 18, *) else { + // For versions below iOS 18, perform the advanced lock state check + if isAdvancedLockEnabled { + #if targetEnvironment(simulator) + let isEnabled = _XCTIsTesting + #else + let isEnabled = true + #endif + await send(.internal(.advancedLockStateLoaded(isEnabled: isEnabled))) + } else { + await send(.internal(.advancedLockStateLoaded(isEnabled: false))) + } + return + } + // Starting with iOS 18, the system-provided biometric check will be used - if #unavailable(iOS 18), isAdvancedLockEnabled { - #if targetEnvironment(simulator) - let isEnabled = _XCTIsTesting - #else - let isEnabled = true - #endif - await send(.internal(.advancedLockStateLoaded(isEnabled: isEnabled))) + if isAdvancedLockEnabled, !userDefaults.appLockMessageShown { + await send(.internal(.showAppLockMessage)) } else { await send(.internal(.advancedLockStateLoaded(isEnabled: false))) } @@ -137,11 +150,9 @@ public struct Splash: Sendable, FeatureReducer { case let .biometricsCheckResult(.failure(error)): state.biometricsCheckFailed = true state.destination = .errorAlert(.init( - title: .init(L10n.Common.errorAlertTitle), - message: .init(error.localizedDescription), - buttons: [ - .default(.init(L10n.Common.ok)), - ] + title: { .init(L10n.Common.errorAlertTitle) }, + actions: { .default(.init(L10n.Common.ok)) }, + message: { .init(error.localizedDescription) } )) return .none @@ -152,17 +163,35 @@ public struct Splash: Sendable, FeatureReducer { } return delegateCompleted(context: state.context) + + case .showAppLockMessage: + state.destination = .errorAlert(.init( + title: { .init(L10n.Biometrics.AppLockAvailableAlert.title) }, + actions: { + .default( + .init(L10n.Common.dismiss), + action: .send(.appLockOkButtonTapped) + ) + }, + message: { .init(L10n.Biometrics.AppLockAvailableAlert.message) } + )) + return .none } } public func reduce(into state: inout State, presentedAction: Destination.Action) -> Effect { switch presentedAction { case .errorAlert(.retryVerifyPasscodeButtonTapped): - verifyPasscode() + return verifyPasscode() + case .errorAlert(.openSettingsButtonTapped): - .run { _ in + return .run { _ in await openURL(URL(string: UIApplication.openSettingsURLString)!) } + + case .errorAlert(.appLockOkButtonTapped): + userDefaults.setAppLockMessageShown(true) + return .send(.internal(.advancedLockStateLoaded(isEnabled: false))) } }