From f1cdf65be727db07a770acbe0467dd0c451a534b Mon Sep 17 00:00:00 2001 From: Brandon Evans Date: Mon, 4 Jan 2021 16:22:09 -0700 Subject: [PATCH 1/4] Add ErrorHandling --- Xcodes.xcodeproj/project.pbxproj | 17 +++++++++++ .../xcshareddata/swiftpm/Package.resolved | 9 ++++++ Xcodes/Resources/Licenses.rtf | 28 +++++++++++++++++++ 3 files changed, 54 insertions(+) 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/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\ \ From a3e910139761327fbaff84b5e47c5702582f6c59 Mon Sep 17 00:00:00 2001 From: Brandon Evans Date: Mon, 4 Jan 2021 16:23:35 -0700 Subject: [PATCH 2/4] Replace .alert with .emittingError --- Xcodes/Backend/AppState+Update.swift | 2 +- Xcodes/Backend/AppState.swift | 13 ++++++++----- Xcodes/Frontend/MainWindow.swift | 7 ++----- Xcodes/Frontend/SignIn/SignIn2FAView.swift | 6 +----- Xcodes/Frontend/SignIn/SignInCredentialsView.swift | 6 +----- Xcodes/Frontend/SignIn/SignInPhoneListView.swift | 6 +----- Xcodes/Frontend/SignIn/SignInSMSView.swift | 6 +----- 7 files changed, 15 insertions(+), 31 deletions(-) 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 }) } } From a0e258a9376d398abf9d88cd48409a24cb85d00e Mon Sep 17 00:00:00 2001 From: Brandon Evans Date: Thu, 7 Jan 2021 20:47:31 -0700 Subject: [PATCH 3/4] Move credential auth error somewhere it won't disappear When signing in, the Sign In button is not always in the view hierarchy, meaning error alerts weren't being shown until the next time the button appeared. --- Xcodes/SettingsView.swift | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Xcodes/SettingsView.swift b/Xcodes/SettingsView.swift index 7b4d9b4..57d9883 100644 --- a/Xcodes/SettingsView.swift +++ b/Xcodes/SettingsView.swift @@ -14,13 +14,13 @@ struct SettingsView: View { 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")) { From 90c067997bdd3e1e3a18c7896baa6d119fcc61ba Mon Sep 17 00:00:00 2001 From: Brandon Evans Date: Thu, 7 Jan 2021 21:13:29 -0700 Subject: [PATCH 4/4] Only show Sign Out button when actually authenticated --- Xcodes/SettingsView.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Xcodes/SettingsView.swift b/Xcodes/SettingsView.swift index 57d9883..6a8212f 100644 --- a/Xcodes/SettingsView.swift +++ b/Xcodes/SettingsView.swift @@ -9,8 +9,8 @@ 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 })