diff --git a/Xcodes.xcodeproj/project.pbxproj b/Xcodes.xcodeproj/project.pbxproj index 998185a..d34ceb7 100644 --- a/Xcodes.xcodeproj/project.pbxproj +++ b/Xcodes.xcodeproj/project.pbxproj @@ -42,6 +42,7 @@ CAA1CB45255A5B60003FD669 /* SignIn2FAView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CAA1CB44255A5B60003FD669 /* SignIn2FAView.swift */; }; CAA1CB49255A5C97003FD669 /* SignInSMSView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CAA1CB48255A5C97003FD669 /* SignInSMSView.swift */; }; CAA1CB4D255A5CFD003FD669 /* SignInPhoneListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CAA1CB4C255A5CFD003FD669 /* SignInPhoneListView.swift */; }; + CAA858CD25A3D8BC00ACF8C0 /* ErrorHandling in Frameworks */ = {isa = PBXBuildFile; productRef = CAA858CC25A3D8BC00ACF8C0 /* ErrorHandling */; }; CABFA9BB2592EEEA00380FEE /* DateFormatter+.swift in Sources */ = {isa = PBXBuildFile; fileRef = CABFA9BA2592EEEA00380FEE /* DateFormatter+.swift */; }; CABFA9BD2592EEEA00380FEE /* Environment.swift in Sources */ = {isa = PBXBuildFile; fileRef = CABFA9A92592EEE900380FEE /* Environment.swift */; }; CABFA9BF2592EEEA00380FEE /* URLSession+DownloadTaskPublisher.swift in Sources */ = {isa = PBXBuildFile; fileRef = CABFA9B32592EEEA00380FEE /* URLSession+DownloadTaskPublisher.swift */; }; @@ -233,6 +234,7 @@ CABFA9FD2592F13300380FEE /* LegibleError in Frameworks */, CA9FF86D25951C6E00E47BAF /* XCModel in Frameworks */, CABFA9F82592F0F900380FEE /* KeychainAccess in Frameworks */, + CAA858CD25A3D8BC00ACF8C0 /* ErrorHandling in Frameworks */, CAA1CB2D255A5262003FD669 /* AppleAPI in Frameworks */, CABFA9DF2592F07A00380FEE /* Path in Frameworks */, CABFA9EE2592F0CC00380FEE /* SwiftSoup in Frameworks */, @@ -508,6 +510,7 @@ CABFA9F72592F0F900380FEE /* KeychainAccess */, CABFA9FC2592F13300380FEE /* LegibleError */, CA9FF86C25951C6E00E47BAF /* XCModel */, + CAA858CC25A3D8BC00ACF8C0 /* ErrorHandling */, ); productName = XcodesMac; productReference = CAD2E79E2449574E00113D76 /* Xcodes.app */; @@ -569,6 +572,7 @@ CABFA9F62592F0F900380FEE /* XCRemoteSwiftPackageReference "KeychainAccess" */, CABFA9FB2592F13300380FEE /* XCRemoteSwiftPackageReference "LegibleError" */, CA9FF86B25951C6E00E47BAF /* XCRemoteSwiftPackageReference "data" */, + CAA858CB25A3D8BC00ACF8C0 /* XCRemoteSwiftPackageReference "ErrorHandling" */, ); productRefGroup = CAD2E79F2449574E00113D76 /* Products */; projectDirPath = ""; @@ -1162,6 +1166,14 @@ revision = b47228c688b608e34b3b84079ab6052a24c7a981; }; }; + CAA858CB25A3D8BC00ACF8C0 /* XCRemoteSwiftPackageReference "ErrorHandling" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/RobotsAndPencils/ErrorHandling"; + requirement = { + kind = upToNextMinorVersion; + minimumVersion = 0.1.0; + }; + }; CABFA9DD2592F07A00380FEE /* XCRemoteSwiftPackageReference "Path" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/mxcl/Path.swift"; @@ -1214,6 +1226,11 @@ isa = XCSwiftPackageProductDependency; productName = AppleAPI; }; + CAA858CC25A3D8BC00ACF8C0 /* ErrorHandling */ = { + isa = XCSwiftPackageProductDependency; + package = CAA858CB25A3D8BC00ACF8C0 /* XCRemoteSwiftPackageReference "ErrorHandling" */; + productName = ErrorHandling; + }; CABFA9DE2592F07A00380FEE /* Path */ = { isa = XCSwiftPackageProductDependency; package = CABFA9DD2592F07A00380FEE /* XCRemoteSwiftPackageReference "Path" */; diff --git a/Xcodes.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Xcodes.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 880f3a4..fdfa23b 100644 --- a/Xcodes.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/Xcodes.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -10,6 +10,15 @@ "version": null } }, + { + "package": "ErrorHandling", + "repositoryURL": "https://github.com/RobotsAndPencils/ErrorHandling", + "state": { + "branch": null, + "revision": "7be837fcb515447c0776805c3288fb7d5181ec68", + "version": "0.1.0" + } + }, { "package": "KeychainAccess", "repositoryURL": "https://github.com/kishikawakatsumi/KeychainAccess", diff --git a/Xcodes/Backend/AppState+Update.swift b/Xcodes/Backend/AppState+Update.swift index 907b7e2..9400bc7 100644 --- a/Xcodes/Backend/AppState+Update.swift +++ b/Xcodes/Backend/AppState+Update.swift @@ -38,7 +38,7 @@ extension AppState { receiveCompletion: { [unowned self] completion in switch completion { case let .failure(error): - self.error = AlertContent(title: "Update Error", message: error.legibleLocalizedDescription) + self.error = error case .finished: Current.defaults.setDate(Current.date(), forKey: "lastUpdated") } diff --git a/Xcodes/Backend/AppState.swift b/Xcodes/Backend/AppState.swift index 4dfa58d..61f559e 100644 --- a/Xcodes/Backend/AppState.swift +++ b/Xcodes/Backend/AppState.swift @@ -27,13 +27,16 @@ class AppState: ObservableObject { } @Published var updatePublisher: AnyCancellable? var isUpdating: Bool { updatePublisher != nil } - @Published var error: AlertContent? - @Published var authError: AlertContent? @Published var presentingSignInAlert = false @Published var isProcessingAuthRequest = false @Published var secondFactorData: SecondFactorData? @Published var xcodeBeingConfirmedForUninstallation: Xcode? @Published var helperInstallState: HelperInstallState = .notInstalled + + // MARK: - Errors + + @Published var error: Error? + @Published var authError: Error? init() { try? loadCachedAvailableXcodes() @@ -158,7 +161,7 @@ class AppState: ObservableObject { } // This error message is not user friendly... need to extract some meaningful data in the different cases - self.authError = AlertContent(title: "Error signing in", message: error.legibleLocalizedDescription) + self.authError = error case .finished: switch self.authenticationState { case .authenticated, .unauthenticated: @@ -220,7 +223,7 @@ class AppState: ObservableObject { .sink( receiveCompletion: { [unowned self] completion in if case let .failure(error) = completion { - self.error = AlertContent(title: "Error uninstalling Xcode", message: error.legibleLocalizedDescription) + self.error = error } self.uninstallPublisher = nil }, @@ -251,7 +254,7 @@ class AppState: ObservableObject { .sink( receiveCompletion: { [unowned self] completion in if case let .failure(error) = completion { - self.error = AlertContent(title: "Error selecting Xcode", message: error.legibleLocalizedDescription) + self.error = error } self.selectPublisher = nil }, diff --git a/Xcodes/Frontend/MainWindow.swift b/Xcodes/Frontend/MainWindow.swift index 5052e77..4e1faf8 100644 --- a/Xcodes/Frontend/MainWindow.swift +++ b/Xcodes/Frontend/MainWindow.swift @@ -1,3 +1,4 @@ +import ErrorHandling import SwiftUI struct MainWindow: View { @@ -31,11 +32,7 @@ struct MainWindow: View { ) .navigationSubtitle(subtitleText) .frame(minWidth: 600, maxWidth: .infinity, minHeight: 300, maxHeight: .infinity) - .alert(item: $appState.error) { error in - Alert(title: Text(error.title), - message: Text(verbatim: error.message), - dismissButton: .default(Text("OK"))) - } + .emittingError($appState.error, recoveryHandler: { _ in }) .sheet(isPresented: $appState.secondFactorData.isNotNil) { secondFactorView(appState.secondFactorData!) .environmentObject(appState) diff --git a/Xcodes/Frontend/SignIn/SignIn2FAView.swift b/Xcodes/Frontend/SignIn/SignIn2FAView.swift index d7fad25..37f4830 100644 --- a/Xcodes/Frontend/SignIn/SignIn2FAView.swift +++ b/Xcodes/Frontend/SignIn/SignIn2FAView.swift @@ -34,11 +34,7 @@ struct SignIn2FAView: View { .frame(height: 25) } .padding() - .alert(item: $appState.authError) { error in - Alert(title: Text(error.title), - message: Text(verbatim: error.message), - dismissButton: .default(Text("OK"))) - } + .emittingError($appState.authError, recoveryHandler: { _ in }) } } diff --git a/Xcodes/Frontend/SignIn/SignInCredentialsView.swift b/Xcodes/Frontend/SignIn/SignInCredentialsView.swift index 89d78e4..accc54a 100644 --- a/Xcodes/Frontend/SignIn/SignInCredentialsView.swift +++ b/Xcodes/Frontend/SignIn/SignInCredentialsView.swift @@ -38,11 +38,7 @@ struct SignInCredentialsView: View { .frame(height: 25) } .padding() - .alert(item: $appState.authError) { error in - Alert(title: Text(error.title), - message: Text(verbatim: error.message), - dismissButton: .default(Text("OK"))) - } + .emittingError($appState.authError, recoveryHandler: { _ in }) } } diff --git a/Xcodes/Frontend/SignIn/SignInPhoneListView.swift b/Xcodes/Frontend/SignIn/SignInPhoneListView.swift index 4e6b730..e2dae0d 100644 --- a/Xcodes/Frontend/SignIn/SignInPhoneListView.swift +++ b/Xcodes/Frontend/SignIn/SignInPhoneListView.swift @@ -39,11 +39,7 @@ struct SignInPhoneListView: View { } .padding() .frame(width: 400, height: 200) - .alert(item: $appState.authError) { error in - Alert(title: Text(error.title), - message: Text(verbatim: error.message), - dismissButton: .default(Text("OK"))) - } + .emittingError($appState.authError, recoveryHandler: { _ in }) } } diff --git a/Xcodes/Frontend/SignIn/SignInSMSView.swift b/Xcodes/Frontend/SignIn/SignInSMSView.swift index 75988d4..51c2c0a 100644 --- a/Xcodes/Frontend/SignIn/SignInSMSView.swift +++ b/Xcodes/Frontend/SignIn/SignInSMSView.swift @@ -34,11 +34,7 @@ struct SignInSMSView: View { .frame(height: 25) } .padding() - .alert(item: $appState.authError) { error in - Alert(title: Text(error.title), - message: Text(verbatim: error.message), - dismissButton: .default(Text("OK"))) - } + .emittingError($appState.authError, recoveryHandler: { _ in }) } } diff --git a/Xcodes/Resources/Licenses.rtf b/Xcodes/Resources/Licenses.rtf index 3dc4006..d706ecb 100644 --- a/Xcodes/Resources/Licenses.rtf +++ b/Xcodes/Resources/Licenses.rtf @@ -31,6 +31,34 @@ SOFTWARE.\ \ \ +\fs34 ErrorHandling\ +\ + +\fs26 MIT License\ +\ +Copyright (c) 2020 Robots and Pencils\ +Copyright (c) 2020 John Sundell\ +\ +Permission is hereby granted, free of charge, to any person obtaining a copy\ +of this software and associated documentation files (the "Software"), to deal\ +in the Software without restriction, including without limitation the rights\ +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ +copies of the Software, and to permit persons to whom the Software is\ +furnished to do so, subject to the following conditions:\ +\ +The above copyright notice and this permission notice shall be included in all\ +copies or substantial portions of the Software.\ +\ +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\ +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\ +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\ +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\ +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\ +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\ +SOFTWARE.\ +\ +\ + \fs34 Path.swift\ \ diff --git a/Xcodes/SettingsView.swift b/Xcodes/SettingsView.swift index 7b4d9b4..6a8212f 100644 --- a/Xcodes/SettingsView.swift +++ b/Xcodes/SettingsView.swift @@ -9,18 +9,18 @@ struct SettingsView: View { VStack(alignment: .leading) { GroupBox(label: Text("Apple ID")) { VStack(alignment: .leading) { - if let username = Current.defaults.string(forKey: "username") { - Text(username) + if appState.authenticationState == .authenticated { + Text(Current.defaults.string(forKey: "username") ?? "-") Button("Sign Out", action: appState.signOut) } else { Button("Sign In", action: { self.appState.presentingSignInAlert = true }) - .sheet(isPresented: $appState.presentingSignInAlert) { - SignInCredentialsView(isPresented: $appState.presentingSignInAlert) - .environmentObject(appState) - } } } .frame(maxWidth: .infinity, alignment: .leading) + .sheet(isPresented: $appState.presentingSignInAlert) { + SignInCredentialsView(isPresented: $appState.presentingSignInAlert) + .environmentObject(appState) + } } GroupBox(label: Text("Data Source")) {