diff --git a/day-068/Projects/PlaceCase/PlaceCase/Data/State/AppState.swift b/day-068/Projects/PlaceCase/PlaceCase/Data/State/AppState.swift index ac4ca7b..a6e30e7 100644 --- a/day-068/Projects/PlaceCase/PlaceCase/Data/State/AppState.swift +++ b/day-068/Projects/PlaceCase/PlaceCase/Data/State/AppState.swift @@ -14,7 +14,7 @@ import LocalAuthentication struct AppState { - var authenticationService: AuthenticatingService + var authenticationService: AuthenticationService var locationCollectionsState: LocationCollectionsState var wikiPagesState: WikiPagesState @@ -24,7 +24,7 @@ struct AppState { init( - authenticationService: AuthenticatingService = AuthenticationService(laContextType: LAContext.self), + authenticationService: AuthenticationService = AuthenticationService(laContextType: LAContext.self), locationCollectionsState: LocationCollectionsState = .init(), wikiPagesState: WikiPagesState = .init() ) { diff --git a/day-068/Projects/PlaceCase/PlaceCase/Reusables/AuthenticationService.swift b/day-068/Projects/PlaceCase/PlaceCase/Reusables/AuthenticationService.swift index 1d34e9e..1433d06 100644 --- a/day-068/Projects/PlaceCase/PlaceCase/Reusables/AuthenticationService.swift +++ b/day-068/Projects/PlaceCase/PlaceCase/Reusables/AuthenticationService.swift @@ -24,19 +24,12 @@ extension LAContext: LAContextType {} -protocol AuthenticatingService { - - /// - Parameter reason: The app-provided reason for requesting authentication, - /// which displays in the authentication dialog presented to the user. - func authenticate(reason: String) -> AnyPublisher -} - - - -final class AuthenticationService: AuthenticatingService { +class AuthenticationService { static let authReason = "Please authenticate to unlock this app and access saved locations." - enum Error: Swift.Error { + enum Error: Swift.Error, Identifiable { + var id: String { self.localizedDescription } + case noBiometricsEnabled(Swift.Error?) case evaluationFailed(Swift.Error?) } @@ -46,12 +39,14 @@ final class AuthenticationService: AuthenticatingService { private var context: LAContextType? - init(laContextType: LAContextType.Type) { + init(laContextType: LAContextType.Type = LAContext.self) { self.laContextType = laContextType } - func authenticate(reason: String) -> AnyPublisher { + /// - Parameter reason: The app-provided reason for requesting authentication, + /// which displays in the authentication dialog presented to the user. + func authenticate(reason: String) -> AnyPublisher { let context = laContextType.init() self.context = context @@ -94,8 +89,8 @@ final class AuthenticationService: AuthenticatingService { extension SampleData { - class AuthService: AuthenticatingService { - func authenticate(reason: String) -> AnyPublisher { + class AuthService: AuthenticationService { + override func authenticate(reason: String) -> AnyPublisher { Empty().eraseToAnyPublisher() } } diff --git a/day-068/Projects/PlaceCase/PlaceCase/Scenes/LocationCollectionsContainerView.swift b/day-068/Projects/PlaceCase/PlaceCase/Scenes/LocationCollectionsContainerView.swift index ac88a6c..f08553e 100644 --- a/day-068/Projects/PlaceCase/PlaceCase/Scenes/LocationCollectionsContainerView.swift +++ b/day-068/Projects/PlaceCase/PlaceCase/Scenes/LocationCollectionsContainerView.swift @@ -30,6 +30,7 @@ extension LocationCollectionsContainerView { } } .navigationBarTitle("PlaceCase") + .alert(item: $viewModel.authenticationError) { _ in self.authenticationErrorAlert } } } @@ -70,6 +71,15 @@ extension LocationCollectionsContainerView { } .padding() } + + + private var authenticationErrorAlert: Alert { + .init( + title: Text(viewModel.authenticationErrorAlertTitle), + message: Text(viewModel.authenticationErrorAlertBody), + dismissButton: .default(Text("OK")) + ) + } } diff --git a/day-068/Projects/PlaceCase/PlaceCase/Scenes/LocationCollectionsContainerViewModel.swift b/day-068/Projects/PlaceCase/PlaceCase/Scenes/LocationCollectionsContainerViewModel.swift index e30637d..85e523c 100644 --- a/day-068/Projects/PlaceCase/PlaceCase/Scenes/LocationCollectionsContainerViewModel.swift +++ b/day-068/Projects/PlaceCase/PlaceCase/Scenes/LocationCollectionsContainerViewModel.swift @@ -14,20 +14,19 @@ import Combine final class LocationCollectionsContainerViewModel: ObservableObject { private var subscriptions = Set() - private let authService: AuthenticatingService + private let authService: AuthenticationService // MARK: - Published Properties @Published var isAuthenticated: Bool = false + @Published var authenticationError: AuthenticationService.Error? = nil // MARK: - Init init( - authService: AuthenticatingService + authService: AuthenticationService ) { self.authService = authService - - setupSubscribers() } } @@ -49,7 +48,7 @@ extension LocationCollectionsContainerViewModel { switch completion { case .failure(let error): DispatchQueue.main.async { [weak self] in - print("Authentication failed: \(error)") + self?.authenticationError = error self?.isAuthenticated = false } default: @@ -67,11 +66,25 @@ extension LocationCollectionsContainerViewModel { } } +// MARK: - Computed +extension LocationCollectionsContainerViewModel { + + var authenticationErrorAlertTitle: String { "Authentication Failed" } + + var authenticationErrorAlertBody: String { + guard let error = authenticationError else { return "" } + + switch error { + case .noBiometricsEnabled: + return "No Biomentric authentication is enabled on this device." + case .evaluationFailed(_): + return "An error occured while attempting evaluation." + } + } +} + // MARK: - Private Helpers private extension LocationCollectionsContainerViewModel { - - func setupSubscribers() { - } }