diff --git a/Fyreplace/Events/Event.swift b/Fyreplace/Events/Event.swift index 8095f70..829e85a 100644 --- a/Fyreplace/Events/Event.swift +++ b/Fyreplace/Events/Event.swift @@ -2,7 +2,9 @@ import SwiftUI protocol Event {} -struct ErrorEvent: Event { +protocol UnfortunateEvent: Event {} + +struct ErrorEvent: UnfortunateEvent { let error: UnexpectedError init(_ error: UnexpectedError) { @@ -14,7 +16,7 @@ extension Event { typealias error = ErrorEvent } -struct FailureEvent: Event { +struct FailureEvent: UnfortunateEvent { let title: LocalizedStringKey let text: LocalizedStringKey } diff --git a/Fyreplace/Views/MainView.swift b/Fyreplace/Views/MainView.swift index 070a8d0..d0e2ebb 100644 --- a/Fyreplace/Views/MainView.swift +++ b/Fyreplace/Views/MainView.swift @@ -1,6 +1,9 @@ import SwiftUI struct MainView: View, MainViewProtocol { + @EnvironmentObject + var eventBus: EventBus + @State var showError = false @@ -13,9 +16,6 @@ struct MainView: View, MainViewProtocol { @State var failures: [FailureEvent] = [] - @EnvironmentObject - private var eventBus: EventBus - @Environment(\.api) private var api diff --git a/Fyreplace/Views/Screens/LoginScreenProtocol.swift b/Fyreplace/Views/Screens/LoginScreenProtocol.swift index e3e5f68..1529ea4 100644 --- a/Fyreplace/Views/Screens/LoginScreenProtocol.swift +++ b/Fyreplace/Views/Screens/LoginScreenProtocol.swift @@ -1,5 +1,4 @@ protocol LoginScreenProtocol: LoadingViewProtocol { - var eventBus: EventBus { get } var api: APIProtocol { get } var identifier: String { get nonmutating set } @@ -15,12 +14,8 @@ extension LoginScreenProtocol { } func submit() async { - await callWhileLoading(failOn: eventBus) { - if isWaitingForRandomCode { - try await createToken() - } else { - try await sendEmail() - } + await callWhileLoading { + try await (isWaitingForRandomCode ? createToken() : sendEmail()) } } @@ -29,37 +24,38 @@ extension LoginScreenProtocol { randomCode = "" } - func sendEmail() async throws { + func sendEmail() async throws -> UnfortunateEvent? { let response = try await api.createNewToken(body: .json(.init(identifier: identifier))) switch response { case .ok: isWaitingForRandomCode = true + return nil case .badRequest: - eventBus.send(.failure( + return .failure( title: "Error.BadRequest.Title", text: "Error.BadRequest.Message" - )) + ) case .forbidden: - eventBus.send(.failure( + return .failure( title: "Error.Forbidden.Title", text: "Error.Forbidden.Message" - )) + ) case .notFound: - eventBus.send(.failure( + return .failure( title: "Login.Error.NotFound.Title", text: "Login.Error.NotFound.Message" - )) + ) case .default: - eventBus.send(.error(UnknownError())) + return .error(UnknownError()) } } - func createToken() async throws { + func createToken() async throws -> UnfortunateEvent? { let response = try await api.createToken(body: .json(.init(identifier: identifier, secret: randomCode))) switch response { @@ -74,22 +70,24 @@ extension LoginScreenProtocol { #if !os(macOS) scheduleTokenRefresh() #endif + + return nil } case .badRequest: - eventBus.send(.failure( + return .failure( title: "Account.Error.CreateToken.BadRequest.Title", text: "Account.Error.CreateToken.BadRequest.Message" - )) + ) case .notFound: - eventBus.send(.failure( + return .failure( title: "Login.Error.NotFound.Title", text: "Login.Error.NotFound.Message" - )) + ) case .default: - eventBus.send(.error(UnknownError())) + return .error(UnknownError()) } } } diff --git a/Fyreplace/Views/Screens/RegisterScreenProtocol.swift b/Fyreplace/Views/Screens/RegisterScreenProtocol.swift index 96d4ce0..562853e 100644 --- a/Fyreplace/Views/Screens/RegisterScreenProtocol.swift +++ b/Fyreplace/Views/Screens/RegisterScreenProtocol.swift @@ -1,5 +1,4 @@ protocol RegisterScreenProtocol: LoadingViewProtocol { - var eventBus: EventBus { get } var api: APIProtocol { get } var username: String { get nonmutating set } @@ -19,12 +18,8 @@ extension RegisterScreenProtocol { } func submit() async { - await callWhileLoading(failOn: eventBus) { - if isWaitingForRandomCode { - try await createToken() - } else { - try await sendEmail() - } + await callWhileLoading { + try await (isWaitingForRandomCode ? createToken() : sendEmail()) } } @@ -34,62 +29,63 @@ extension RegisterScreenProtocol { isRegistering = false } - func sendEmail() async throws { + func sendEmail() async throws -> UnfortunateEvent? { let response = try await api.createUser(body: .json(.init(email: email, username: username))) switch response { case .created: isWaitingForRandomCode = true isRegistering = true + return nil case let .badRequest(badRequest): switch badRequest.body { case let .json(json) where json.violations?.first?.field == "createUser.input.username": - eventBus.send(.failure( + return .failure( title: "Register.Error.CreateUser.BadRequest.Username.Title", text: "Register.Error.CreateUser.BadRequest.Username.Message" - )) + ) case let .json(json) where json.violations?.first?.field == "createUser.input.email": - eventBus.send(.failure( + return .failure( title: "Register.Error.CreateUser.BadRequest.Email.Title", text: "Register.Error.CreateUser.BadRequest.Email.Message" - )) + ) case .json: - eventBus.send(.failure( + return .failure( title: "Error.BadRequest.Title", text: "Error.BadRequest.Message" - )) + ) } case .forbidden: - eventBus.send(.failure( + return .failure( title: "Register.Error.CreateUser.Forbidden.Title", text: "Register.Error.CreateUser.Forbidden.Message" - )) + ) case let .conflict(conflict): switch conflict.body { case let .json(explanation) where explanation.reason == "username_taken": - eventBus.send(.failure( + return .failure( title: "Register.Error.CreateUser.Conflict.Username.Title", text: "Register.Error.CreateUser.Conflict.Username.Message" - )) + ) case .json: - eventBus.send(.failure( + return .failure( title: "Register.Error.CreateUser.Conflict.Email.Title", text: "Register.Error.CreateUser.Conflict.Email.Message" - )) + ) } case .default: - eventBus.send(.error(UnknownError())) + return .error(UnknownError()) } } - func createToken() async throws { + func createToken() async throws -> UnfortunateEvent? { let response = try await api.createToken(body: .json(.init(identifier: email, secret: randomCode))) switch response { @@ -106,22 +102,24 @@ extension RegisterScreenProtocol { #if !os(macOS) scheduleTokenRefresh() #endif + + return nil } case .badRequest: - eventBus.send(.failure( + return .failure( title: "Account.Error.CreateToken.BadRequest.Title", text: "Account.Error.CreateToken.BadRequest.Message" - )) + ) case .notFound: - eventBus.send(.failure( + return .failure( title: "Register.Error.CreateToken.NotFound.Title", text: "Register.Error.CreateToken.NotFound.Message" - )) + ) case .default: - eventBus.send(.error(UnknownError())) + return .error(UnknownError()) } } } diff --git a/Fyreplace/Views/Screens/SettingsScreen.swift b/Fyreplace/Views/Screens/SettingsScreen.swift index d8b4b4e..25b6b11 100644 --- a/Fyreplace/Views/Screens/SettingsScreen.swift +++ b/Fyreplace/Views/Screens/SettingsScreen.swift @@ -1,6 +1,9 @@ import SwiftUI struct SettingsScreen: View, SettingsScreenProtocol { + @EnvironmentObject + var eventBus: EventBus + @KeychainStorage("connection.token") var token diff --git a/Fyreplace/Views/ViewProtocol.swift b/Fyreplace/Views/ViewProtocol.swift index dbad0ae..a588982 100644 --- a/Fyreplace/Views/ViewProtocol.swift +++ b/Fyreplace/Views/ViewProtocol.swift @@ -1,7 +1,9 @@ import OpenAPIRuntime import Sentry -protocol ViewProtocol {} +protocol ViewProtocol { + var eventBus: EventBus { get } +} protocol LoadingViewProtocol: ViewProtocol { var isLoading: Bool { get nonmutating set } @@ -9,23 +11,32 @@ protocol LoadingViewProtocol: ViewProtocol { @MainActor extension ViewProtocol { - func call(failOn eventBus: EventBus, action: () async throws -> Void) async { + func call(action: () async throws -> UnfortunateEvent?) async { + let unfortunateEvent: UnfortunateEvent? + do { - try await action() + unfortunateEvent = try await action() } catch is ClientError { - eventBus.send(.error(ConnectionError())) + unfortunateEvent = .error(ConnectionError()) } catch { - eventBus.send(.error(UnknownError())) - SentrySDK.capture(error: error) + unfortunateEvent = .error(UnknownError()) + } + + if let event = unfortunateEvent { + eventBus.send(event) + + if let event = unfortunateEvent as? ErrorEvent { + SentrySDK.capture(error: event.error) + } } } } @MainActor extension LoadingViewProtocol { - func callWhileLoading(failOn eventBus: EventBus, action: () async throws -> Void) async { + func callWhileLoading(action: () async throws -> UnfortunateEvent?) async { isLoading = true - await call(failOn: eventBus, action: action) + await call(action: action) isLoading = false } }