From 4e03c59d0a403349add85cd292fb2dc92754476b Mon Sep 17 00:00:00 2001 From: Chad Sykes Date: Sun, 27 Dec 2020 12:22:12 -0700 Subject: [PATCH 1/7] Disable the action button when the AppState isProcessingRequest --- Xcodes.xcodeproj/project.pbxproj | 2 +- Xcodes/Backend/AppState.swift | 7 +++++++ Xcodes/Frontend/SignIn/SignIn2FAView.swift | 2 +- Xcodes/Frontend/SignIn/SignInCredentialsView.swift | 2 +- Xcodes/Frontend/SignIn/SignInPhoneListView.swift | 2 +- Xcodes/Frontend/SignIn/SignInSMSView.swift | 2 +- 6 files changed, 12 insertions(+), 5 deletions(-) diff --git a/Xcodes.xcodeproj/project.pbxproj b/Xcodes.xcodeproj/project.pbxproj index b855b63..d1a6c21 100644 --- a/Xcodes.xcodeproj/project.pbxproj +++ b/Xcodes.xcodeproj/project.pbxproj @@ -164,9 +164,9 @@ children = ( CA735108257BF96D00EA9CF8 /* AttributedText.swift */, CA73510C257BFCEF00EA9CF8 /* NSAttributedString+.swift */, + CA5D781D257365D6008EDE9D /* PinCodeTextView.swift */, CAA1CB34255A5AD5003FD669 /* SignInCredentialsView.swift */, CAA1CB44255A5B60003FD669 /* SignIn2FAView.swift */, - CA5D781D257365D6008EDE9D /* PinCodeTextView.swift */, CAA1CB48255A5C97003FD669 /* SignInSMSView.swift */, CAA1CB4C255A5CFD003FD669 /* SignInPhoneListView.swift */, ); diff --git a/Xcodes/Backend/AppState.swift b/Xcodes/Backend/AppState.swift index 4ee7fb3..38fd512 100644 --- a/Xcodes/Backend/AppState.swift +++ b/Xcodes/Backend/AppState.swift @@ -15,6 +15,7 @@ class AppState: ObservableObject { @Published var allVersions: [XcodeVersion] = [] @Published var error: AlertContent? @Published var presentingSignInAlert = false + @Published var isProcessingRequest = false @Published var secondFactorData: SecondFactorData? // MARK: - Authentication @@ -62,11 +63,13 @@ class AppState: ObservableObject { try? Current.keychain.set(password, key: username) Current.defaults.set(username, forKey: "username") + isProcessingRequest = true return client.login(accountName: username, password: password) .receive(on: DispatchQueue.main) .handleEvents( receiveOutput: { authenticationState in self.authenticationState = authenticationState + self.isProcessingRequest = false }, receiveCompletion: { completion in self.handleAuthenticationFlowCompletion(completion) @@ -85,10 +88,12 @@ class AppState: ObservableObject { } func requestSMS(to trustedPhoneNumber: AuthOptionsResponse.TrustedPhoneNumber, authOptions: AuthOptionsResponse, sessionData: AppleSessionData) { + isProcessingRequest = true client.requestSMSSecurityCode(to: trustedPhoneNumber, authOptions: authOptions, sessionData: sessionData) .sink( receiveCompletion: { completion in self.handleAuthenticationFlowCompletion(completion) + self.isProcessingRequest = false }, receiveValue: { authenticationState in self.authenticationState = authenticationState @@ -105,11 +110,13 @@ class AppState: ObservableObject { } func submitSecurityCode(_ code: SecurityCode, sessionData: AppleSessionData) { + isProcessingRequest = true client.submitSecurityCode(code, sessionData: sessionData) .receive(on: DispatchQueue.main) .sink( receiveCompletion: { completion in self.handleAuthenticationFlowCompletion(completion) + self.isProcessingRequest = false }, receiveValue: { authenticationState in self.authenticationState = authenticationState diff --git a/Xcodes/Frontend/SignIn/SignIn2FAView.swift b/Xcodes/Frontend/SignIn/SignIn2FAView.swift index 329a108..d49f088 100644 --- a/Xcodes/Frontend/SignIn/SignIn2FAView.swift +++ b/Xcodes/Frontend/SignIn/SignIn2FAView.swift @@ -26,7 +26,7 @@ struct SignIn2FAView: View { Spacer() Button("Continue", action: { appState.submitSecurityCode(.device(code: code), sessionData: sessionData) }) .keyboardShortcut(.defaultAction) - .disabled(code.count != authOptions.securityCode.length) + .disabled(code.count != authOptions.securityCode.length || appState.isProcessingRequest) } } .padding() diff --git a/Xcodes/Frontend/SignIn/SignInCredentialsView.swift b/Xcodes/Frontend/SignIn/SignInCredentialsView.swift index 7b7c49c..f4f16db 100644 --- a/Xcodes/Frontend/SignIn/SignInCredentialsView.swift +++ b/Xcodes/Frontend/SignIn/SignInCredentialsView.swift @@ -29,7 +29,7 @@ struct SignInCredentialsView: View { Button("Cancel") { isPresented = false } .keyboardShortcut(.cancelAction) Button("Next") { appState.signIn(username: username, password: password) } - .disabled(username.isEmpty) + .disabled(username.isEmpty || appState.isProcessingRequest) .keyboardShortcut(.defaultAction) } } diff --git a/Xcodes/Frontend/SignIn/SignInPhoneListView.swift b/Xcodes/Frontend/SignIn/SignInPhoneListView.swift index 562882d..2fe8faf 100644 --- a/Xcodes/Frontend/SignIn/SignInPhoneListView.swift +++ b/Xcodes/Frontend/SignIn/SignInPhoneListView.swift @@ -30,7 +30,7 @@ struct SignInPhoneListView: View { Spacer() Button("Continue", action: { appState.requestSMS(to: authOptions.trustedPhoneNumbers!.first { $0.id == selectedPhoneNumberID }!, authOptions: authOptions, sessionData: sessionData) }) .keyboardShortcut(.defaultAction) - .disabled(selectedPhoneNumberID == nil) + .disabled(selectedPhoneNumberID == nil || appState.isProcessingRequest) } } .padding() diff --git a/Xcodes/Frontend/SignIn/SignInSMSView.swift b/Xcodes/Frontend/SignIn/SignInSMSView.swift index 11804b5..1fe1e9b 100644 --- a/Xcodes/Frontend/SignIn/SignInSMSView.swift +++ b/Xcodes/Frontend/SignIn/SignInSMSView.swift @@ -26,7 +26,7 @@ struct SignInSMSView: View { Spacer() Button("Continue", action: { appState.submitSecurityCode(.sms(code: code, phoneNumberId: trustedPhoneNumber.id), sessionData: sessionData) }) .keyboardShortcut(.defaultAction) - .disabled(code.count != authOptions.securityCode.length) + .disabled(code.count != authOptions.securityCode.length || appState.isProcessingRequest) } } .padding() From ff1d2546dc1cc3a409afaced864d58dd2e63b110 Mon Sep 17 00:00:00 2001 From: Chad Sykes Date: Sun, 27 Dec 2020 13:10:12 -0700 Subject: [PATCH 2/7] Switch out the action button with an activity spinner when processing a request --- Xcodes/Frontend/SignIn/SignIn2FAView.swift | 14 +++++++++++--- .../Frontend/SignIn/SignInCredentialsView.swift | 14 +++++++++++--- Xcodes/Frontend/SignIn/SignInPhoneListView.swift | 15 ++++++++++++--- Xcodes/Frontend/SignIn/SignInSMSView.swift | 14 +++++++++++--- 4 files changed, 45 insertions(+), 12 deletions(-) diff --git a/Xcodes/Frontend/SignIn/SignIn2FAView.swift b/Xcodes/Frontend/SignIn/SignIn2FAView.swift index d49f088..b30cf2e 100644 --- a/Xcodes/Frontend/SignIn/SignIn2FAView.swift +++ b/Xcodes/Frontend/SignIn/SignIn2FAView.swift @@ -24,10 +24,18 @@ struct SignIn2FAView: View { .keyboardShortcut(.cancelAction) Button("Send SMS", action: { appState.choosePhoneNumberForSMS(authOptions: authOptions, sessionData: sessionData) }) Spacer() - Button("Continue", action: { appState.submitSecurityCode(.device(code: code), sessionData: sessionData) }) - .keyboardShortcut(.defaultAction) - .disabled(code.count != authOptions.securityCode.length || appState.isProcessingRequest) + if appState.isProcessingRequest { + ProgressView() + .progressViewStyle(CircularProgressViewStyle()) + .scaleEffect(x: 0.5, y: 0.5, anchor: .center) + .padding(.trailing, 22) + } else { + Button("Continue", action: { appState.submitSecurityCode(.device(code: code), sessionData: sessionData) }) + .keyboardShortcut(.defaultAction) + .disabled(code.count != authOptions.securityCode.length) + } } + .frame(height: 25) } .padding() } diff --git a/Xcodes/Frontend/SignIn/SignInCredentialsView.swift b/Xcodes/Frontend/SignIn/SignInCredentialsView.swift index f4f16db..72c9b6b 100644 --- a/Xcodes/Frontend/SignIn/SignInCredentialsView.swift +++ b/Xcodes/Frontend/SignIn/SignInCredentialsView.swift @@ -28,10 +28,18 @@ struct SignInCredentialsView: View { Spacer() Button("Cancel") { isPresented = false } .keyboardShortcut(.cancelAction) - Button("Next") { appState.signIn(username: username, password: password) } - .disabled(username.isEmpty || appState.isProcessingRequest) - .keyboardShortcut(.defaultAction) + if appState.isProcessingRequest { + ProgressView() + .progressViewStyle(CircularProgressViewStyle()) + .scaleEffect(x: 0.5, y: 0.5, anchor: .center) + .padding(.horizontal, 8) + } else { + Button("Next") { appState.signIn(username: username, password: password) } + .disabled(username.isEmpty) + .keyboardShortcut(.defaultAction) + } } + .frame(height: 25) } .padding() } diff --git a/Xcodes/Frontend/SignIn/SignInPhoneListView.swift b/Xcodes/Frontend/SignIn/SignInPhoneListView.swift index 2fe8faf..213a4c0 100644 --- a/Xcodes/Frontend/SignIn/SignInPhoneListView.swift +++ b/Xcodes/Frontend/SignIn/SignInPhoneListView.swift @@ -28,10 +28,19 @@ struct SignInPhoneListView: View { Button("Cancel", action: { isPresented = false }) .keyboardShortcut(.cancelAction) Spacer() - Button("Continue", action: { appState.requestSMS(to: authOptions.trustedPhoneNumbers!.first { $0.id == selectedPhoneNumberID }!, authOptions: authOptions, sessionData: sessionData) }) - .keyboardShortcut(.defaultAction) - .disabled(selectedPhoneNumberID == nil || appState.isProcessingRequest) + + if appState.isProcessingRequest { + ProgressView() + .progressViewStyle(CircularProgressViewStyle()) + .scaleEffect(x: 0.5, y: 0.5, anchor: .center) + .padding(.trailing, 22) + } else { + Button("Continue", action: { appState.requestSMS(to: authOptions.trustedPhoneNumbers!.first { $0.id == selectedPhoneNumberID }!, authOptions: authOptions, sessionData: sessionData) }) + .keyboardShortcut(.defaultAction) + .disabled(selectedPhoneNumberID == nil) + } } + .frame(height: 25) } .padding() } diff --git a/Xcodes/Frontend/SignIn/SignInSMSView.swift b/Xcodes/Frontend/SignIn/SignInSMSView.swift index 1fe1e9b..997e6e0 100644 --- a/Xcodes/Frontend/SignIn/SignInSMSView.swift +++ b/Xcodes/Frontend/SignIn/SignInSMSView.swift @@ -24,10 +24,18 @@ struct SignInSMSView: View { Button("Cancel", action: { isPresented = false }) .keyboardShortcut(.cancelAction) Spacer() - Button("Continue", action: { appState.submitSecurityCode(.sms(code: code, phoneNumberId: trustedPhoneNumber.id), sessionData: sessionData) }) - .keyboardShortcut(.defaultAction) - .disabled(code.count != authOptions.securityCode.length || appState.isProcessingRequest) + if appState.isProcessingRequest { + ProgressView() + .progressViewStyle(CircularProgressViewStyle()) + .scaleEffect(x: 0.5, y: 0.5, anchor: .center) + .padding(.trailing, 22) + } else { + Button("Continue", action: { appState.submitSecurityCode(.sms(code: code, phoneNumberId: trustedPhoneNumber.id), sessionData: sessionData) }) + .keyboardShortcut(.defaultAction) + .disabled(code.count != authOptions.securityCode.length) + } } + .frame(height: 25) } .padding() } From fb6dd8fac8d3572fa907b2e8e408cd4e2fe194be Mon Sep 17 00:00:00 2001 From: Chad Sykes Date: Sun, 27 Dec 2020 13:10:36 -0700 Subject: [PATCH 3/7] Fix some layout issues to improve readability --- Xcodes/Frontend/SignIn/SignInPhoneListView.swift | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/Xcodes/Frontend/SignIn/SignInPhoneListView.swift b/Xcodes/Frontend/SignIn/SignInPhoneListView.swift index 213a4c0..5a09788 100644 --- a/Xcodes/Frontend/SignIn/SignInPhoneListView.swift +++ b/Xcodes/Frontend/SignIn/SignInPhoneListView.swift @@ -16,12 +16,12 @@ struct SignInPhoneListView: View { List(phoneNumbers, selection: $selectedPhoneNumberID) { Text($0.numberWithDialCode) } - .frame(height: 200) } else { AttributedText( - NSAttributedString(string: "Your account doesn't have any trusted phone numbers, but they're required for two-factor authentication. See https://support.apple.com/en-ca/HT204915.") + NSAttributedString(string: "Your account doesn't have any trusted phone numbers, but they're required for two-factor authentication.\n\nSee https://support.apple.com/en-ca/HT204915.") .convertingURLsToLinkAttributes() ) + Spacer() } HStack { @@ -42,6 +42,7 @@ struct SignInPhoneListView: View { } .frame(height: 25) } + .frame(width: 400, height: 200) .padding() } } @@ -57,7 +58,8 @@ struct SignInPhoneListView_Previews: PreviewProvider { securityCode: .init(length: 6)), sessionData: AppleSessionData(serviceKey: "", sessionID: "", scnt: "") ) - + .environmentObject(AppState()) + SignInPhoneListView( isPresented: .constant(true), authOptions: AuthOptionsResponse( @@ -66,6 +68,7 @@ struct SignInPhoneListView_Previews: PreviewProvider { securityCode: .init(length: 6)), sessionData: AppleSessionData(serviceKey: "", sessionID: "", scnt: "") ) + .environmentObject(AppState()) } } } From c9c31ff6f5e461c6a7453ed068bf8959395855c3 Mon Sep 17 00:00:00 2001 From: Chad Sykes Date: Sun, 27 Dec 2020 13:21:07 -0700 Subject: [PATCH 4/7] Fix warning about executing updating the UI from outside the main thread --- Xcodes/Backend/AppState.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/Xcodes/Backend/AppState.swift b/Xcodes/Backend/AppState.swift index 38fd512..8c6e686 100644 --- a/Xcodes/Backend/AppState.swift +++ b/Xcodes/Backend/AppState.swift @@ -90,6 +90,7 @@ class AppState: ObservableObject { func requestSMS(to trustedPhoneNumber: AuthOptionsResponse.TrustedPhoneNumber, authOptions: AuthOptionsResponse, sessionData: AppleSessionData) { isProcessingRequest = true client.requestSMSSecurityCode(to: trustedPhoneNumber, authOptions: authOptions, sessionData: sessionData) + .receive(on: DispatchQueue.main) .sink( receiveCompletion: { completion in self.handleAuthenticationFlowCompletion(completion) From 74cb2198f591a418795c6a7643458947c195ffed Mon Sep 17 00:00:00 2001 From: Chad Sykes Date: Sun, 27 Dec 2020 13:36:28 -0700 Subject: [PATCH 5/7] Add missing error alerts to the SignIn views (this does not fix the error formatting from AppState for fear of merge issues) --- Xcodes/Backend/AppState.swift | 6 ++++-- Xcodes/Frontend/SignIn/SignIn2FAView.swift | 5 +++++ Xcodes/Frontend/SignIn/SignInCredentialsView.swift | 5 +++++ Xcodes/Frontend/SignIn/SignInPhoneListView.swift | 7 ++++++- Xcodes/Frontend/SignIn/SignInSMSView.swift | 5 +++++ 5 files changed, 25 insertions(+), 3 deletions(-) diff --git a/Xcodes/Backend/AppState.swift b/Xcodes/Backend/AppState.swift index 8c6e686..4764666 100644 --- a/Xcodes/Backend/AppState.swift +++ b/Xcodes/Backend/AppState.swift @@ -14,6 +14,7 @@ class AppState: ObservableObject { @Published var authenticationState: AuthenticationState = .unauthenticated @Published var allVersions: [XcodeVersion] = [] @Published var error: AlertContent? + @Published var authError: AlertContent? @Published var presentingSignInAlert = false @Published var isProcessingRequest = false @Published var secondFactorData: SecondFactorData? @@ -134,8 +135,9 @@ class AppState: ObservableObject { // remove any keychain password if we fail to log with an invalid username or password so it doesn't try again. try? Current.keychain.remove(username) } - - self.error = AlertContent(title: "Error signing in", message: error.legibleLocalizedDescription) + + // 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) case .finished: switch self.authenticationState { case .authenticated, .unauthenticated: diff --git a/Xcodes/Frontend/SignIn/SignIn2FAView.swift b/Xcodes/Frontend/SignIn/SignIn2FAView.swift index b30cf2e..2fb16a5 100644 --- a/Xcodes/Frontend/SignIn/SignIn2FAView.swift +++ b/Xcodes/Frontend/SignIn/SignIn2FAView.swift @@ -38,6 +38,11 @@ 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"))) + } } } diff --git a/Xcodes/Frontend/SignIn/SignInCredentialsView.swift b/Xcodes/Frontend/SignIn/SignInCredentialsView.swift index 72c9b6b..61e0389 100644 --- a/Xcodes/Frontend/SignIn/SignInCredentialsView.swift +++ b/Xcodes/Frontend/SignIn/SignInCredentialsView.swift @@ -42,6 +42,11 @@ 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"))) + } } } diff --git a/Xcodes/Frontend/SignIn/SignInPhoneListView.swift b/Xcodes/Frontend/SignIn/SignInPhoneListView.swift index 5a09788..82072f8 100644 --- a/Xcodes/Frontend/SignIn/SignInPhoneListView.swift +++ b/Xcodes/Frontend/SignIn/SignInPhoneListView.swift @@ -42,8 +42,13 @@ struct SignInPhoneListView: View { } .frame(height: 25) } - .frame(width: 400, height: 200) .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"))) + } } } diff --git a/Xcodes/Frontend/SignIn/SignInSMSView.swift b/Xcodes/Frontend/SignIn/SignInSMSView.swift index 997e6e0..552a73b 100644 --- a/Xcodes/Frontend/SignIn/SignInSMSView.swift +++ b/Xcodes/Frontend/SignIn/SignInSMSView.swift @@ -38,6 +38,11 @@ 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"))) + } } } From 6cddffbef362e59ca0fe185f0605a96d155f82de Mon Sep 17 00:00:00 2001 From: Chad Sykes Date: Sun, 27 Dec 2020 16:13:36 -0700 Subject: [PATCH 6/7] Convert the common code into a ProgressButton --- Xcodes.xcodeproj/project.pbxproj | 12 +++++++ Xcodes/Frontend/Common/ProgressButton.swift | 34 +++++++++++++++++++ Xcodes/Frontend/SignIn/SignIn2FAView.swift | 14 +++----- .../SignIn/SignInCredentialsView.swift | 14 +++----- .../Frontend/SignIn/SignInPhoneListView.swift | 15 +++----- Xcodes/Frontend/SignIn/SignInSMSView.swift | 14 +++----- 6 files changed, 66 insertions(+), 37 deletions(-) create mode 100644 Xcodes/Frontend/Common/ProgressButton.swift diff --git a/Xcodes.xcodeproj/project.pbxproj b/Xcodes.xcodeproj/project.pbxproj index d1a6c21..85fea2d 100644 --- a/Xcodes.xcodeproj/project.pbxproj +++ b/Xcodes.xcodeproj/project.pbxproj @@ -7,6 +7,7 @@ objects = { /* Begin PBXBuildFile section */ + 63EAA4EB259944450046AB8F /* ProgressButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 63EAA4EA259944450046AB8F /* ProgressButton.swift */; }; CA378F992466567600A58CE0 /* AppState.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA378F982466567600A58CE0 /* AppState.swift */; }; CA39711924495F0E00AFFB77 /* AppStoreButtonStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA39711824495F0E00AFFB77 /* AppStoreButtonStyle.swift */; }; CA44901F2463AD34003D8213 /* Tag.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA44901E2463AD34003D8213 /* Tag.swift */; }; @@ -64,6 +65,7 @@ /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ + 63EAA4EA259944450046AB8F /* ProgressButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProgressButton.swift; sourceTree = ""; }; CA378F982466567600A58CE0 /* AppState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppState.swift; sourceTree = ""; }; CA39711824495F0E00AFFB77 /* AppStoreButtonStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppStoreButtonStyle.swift; sourceTree = ""; }; CA44901E2463AD34003D8213 /* Tag.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Tag.swift; sourceTree = ""; }; @@ -142,6 +144,14 @@ /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 63EAA4E9259944340046AB8F /* Common */ = { + isa = PBXGroup; + children = ( + 63EAA4EA259944450046AB8F /* ProgressButton.swift */, + ); + path = Common; + sourceTree = ""; + }; CA538A12255A4F7C00E64DD7 /* Frameworks */ = { isa = PBXGroup; children = ( @@ -210,6 +220,7 @@ CABFAA1A2592F7D900380FEE /* Frontend */ = { isa = PBXGroup; children = ( + 63EAA4E9259944340046AB8F /* Common */, CA9FF8552595082000E47BAF /* About */, CAA1CB50255A5D16003FD669 /* SignIn */, CABFAA142592F73000380FEE /* XcodeList */, @@ -458,6 +469,7 @@ CABFAA432593104F00380FEE /* AboutView.swift in Sources */, CABFA9CC2592EEEA00380FEE /* Path+.swift in Sources */, CAD2E7A22449574E00113D76 /* XcodesApp.swift in Sources */, + 63EAA4EB259944450046AB8F /* ProgressButton.swift in Sources */, CA5D781E257365D6008EDE9D /* PinCodeTextView.swift in Sources */, CA39711924495F0E00AFFB77 /* AppStoreButtonStyle.swift in Sources */, ); diff --git a/Xcodes/Frontend/Common/ProgressButton.swift b/Xcodes/Frontend/Common/ProgressButton.swift new file mode 100644 index 0000000..2b6668d --- /dev/null +++ b/Xcodes/Frontend/Common/ProgressButton.swift @@ -0,0 +1,34 @@ +// +// ProgressButton.swift +// Xcodes +// +// Created by Chad Sykes on 2020-12-27. +// Copyright © 2020 Robots and Pencils. All rights reserved. +// + +import SwiftUI + +struct ProgressButton: View { + let isInProgress: Bool + let action: () -> Void + let label: () -> Label + + init(isInProgress: Bool, action: @escaping () -> Void, @ViewBuilder label: @escaping () -> Label) { + self.isInProgress = isInProgress + self.action = action + self.label = label + } + + var body: some View { + Button(action: action) { + if isInProgress { + ProgressView() + .progressViewStyle(CircularProgressViewStyle()) + .scaleEffect(x: 0.5, y: 0.5, anchor: .center) + } else { + label() + } + } + .disabled(isInProgress) + } +} diff --git a/Xcodes/Frontend/SignIn/SignIn2FAView.swift b/Xcodes/Frontend/SignIn/SignIn2FAView.swift index 2fb16a5..d7fad25 100644 --- a/Xcodes/Frontend/SignIn/SignIn2FAView.swift +++ b/Xcodes/Frontend/SignIn/SignIn2FAView.swift @@ -24,16 +24,12 @@ struct SignIn2FAView: View { .keyboardShortcut(.cancelAction) Button("Send SMS", action: { appState.choosePhoneNumberForSMS(authOptions: authOptions, sessionData: sessionData) }) Spacer() - if appState.isProcessingRequest { - ProgressView() - .progressViewStyle(CircularProgressViewStyle()) - .scaleEffect(x: 0.5, y: 0.5, anchor: .center) - .padding(.trailing, 22) - } else { - Button("Continue", action: { appState.submitSecurityCode(.device(code: code), sessionData: sessionData) }) - .keyboardShortcut(.defaultAction) - .disabled(code.count != authOptions.securityCode.length) + ProgressButton(isInProgress: appState.isProcessingAuthRequest, + action: { appState.submitSecurityCode(.device(code: code), sessionData: sessionData) }) { + Text("Continue") } + .keyboardShortcut(.defaultAction) + .disabled(code.count != authOptions.securityCode.length) } .frame(height: 25) } diff --git a/Xcodes/Frontend/SignIn/SignInCredentialsView.swift b/Xcodes/Frontend/SignIn/SignInCredentialsView.swift index 61e0389..89d78e4 100644 --- a/Xcodes/Frontend/SignIn/SignInCredentialsView.swift +++ b/Xcodes/Frontend/SignIn/SignInCredentialsView.swift @@ -28,16 +28,12 @@ struct SignInCredentialsView: View { Spacer() Button("Cancel") { isPresented = false } .keyboardShortcut(.cancelAction) - if appState.isProcessingRequest { - ProgressView() - .progressViewStyle(CircularProgressViewStyle()) - .scaleEffect(x: 0.5, y: 0.5, anchor: .center) - .padding(.horizontal, 8) - } else { - Button("Next") { appState.signIn(username: username, password: password) } - .disabled(username.isEmpty) - .keyboardShortcut(.defaultAction) + ProgressButton(isInProgress: appState.isProcessingAuthRequest, + action: { appState.signIn(username: username, password: password) }) { + Text("Next") } + .disabled(username.isEmpty || password.isEmpty) + .keyboardShortcut(.defaultAction) } .frame(height: 25) } diff --git a/Xcodes/Frontend/SignIn/SignInPhoneListView.swift b/Xcodes/Frontend/SignIn/SignInPhoneListView.swift index 82072f8..4e6b730 100644 --- a/Xcodes/Frontend/SignIn/SignInPhoneListView.swift +++ b/Xcodes/Frontend/SignIn/SignInPhoneListView.swift @@ -28,17 +28,12 @@ struct SignInPhoneListView: View { Button("Cancel", action: { isPresented = false }) .keyboardShortcut(.cancelAction) Spacer() - - if appState.isProcessingRequest { - ProgressView() - .progressViewStyle(CircularProgressViewStyle()) - .scaleEffect(x: 0.5, y: 0.5, anchor: .center) - .padding(.trailing, 22) - } else { - Button("Continue", action: { appState.requestSMS(to: authOptions.trustedPhoneNumbers!.first { $0.id == selectedPhoneNumberID }!, authOptions: authOptions, sessionData: sessionData) }) - .keyboardShortcut(.defaultAction) - .disabled(selectedPhoneNumberID == nil) + ProgressButton(isInProgress: appState.isProcessingAuthRequest, + action: { appState.requestSMS(to: authOptions.trustedPhoneNumbers!.first { $0.id == selectedPhoneNumberID }!, authOptions: authOptions, sessionData: sessionData) }) { + Text("Continue") } + .keyboardShortcut(.defaultAction) + .disabled(selectedPhoneNumberID == nil) } .frame(height: 25) } diff --git a/Xcodes/Frontend/SignIn/SignInSMSView.swift b/Xcodes/Frontend/SignIn/SignInSMSView.swift index 552a73b..75988d4 100644 --- a/Xcodes/Frontend/SignIn/SignInSMSView.swift +++ b/Xcodes/Frontend/SignIn/SignInSMSView.swift @@ -24,16 +24,12 @@ struct SignInSMSView: View { Button("Cancel", action: { isPresented = false }) .keyboardShortcut(.cancelAction) Spacer() - if appState.isProcessingRequest { - ProgressView() - .progressViewStyle(CircularProgressViewStyle()) - .scaleEffect(x: 0.5, y: 0.5, anchor: .center) - .padding(.trailing, 22) - } else { - Button("Continue", action: { appState.submitSecurityCode(.sms(code: code, phoneNumberId: trustedPhoneNumber.id), sessionData: sessionData) }) - .keyboardShortcut(.defaultAction) - .disabled(code.count != authOptions.securityCode.length) + ProgressButton(isInProgress: appState.isProcessingAuthRequest, + action: { appState.submitSecurityCode(.sms(code: code, phoneNumberId: trustedPhoneNumber.id), sessionData: sessionData) }) { + Text("Continue") } + .keyboardShortcut(.defaultAction) + .disabled(code.count != authOptions.securityCode.length) } .frame(height: 25) } From ee10d2f92bb3ab548a22d05987fdad9cc906a1d9 Mon Sep 17 00:00:00 2001 From: Chad Sykes Date: Sun, 27 Dec 2020 16:14:10 -0700 Subject: [PATCH 7/7] Some variable renaming based on PR feedback --- Xcodes/Backend/AppState.swift | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Xcodes/Backend/AppState.swift b/Xcodes/Backend/AppState.swift index 4764666..a554e15 100644 --- a/Xcodes/Backend/AppState.swift +++ b/Xcodes/Backend/AppState.swift @@ -16,7 +16,7 @@ class AppState: ObservableObject { @Published var error: AlertContent? @Published var authError: AlertContent? @Published var presentingSignInAlert = false - @Published var isProcessingRequest = false + @Published var isProcessingAuthRequest = false @Published var secondFactorData: SecondFactorData? // MARK: - Authentication @@ -64,16 +64,16 @@ class AppState: ObservableObject { try? Current.keychain.set(password, key: username) Current.defaults.set(username, forKey: "username") - isProcessingRequest = true + isProcessingAuthRequest = true return client.login(accountName: username, password: password) .receive(on: DispatchQueue.main) .handleEvents( receiveOutput: { authenticationState in self.authenticationState = authenticationState - self.isProcessingRequest = false }, receiveCompletion: { completion in self.handleAuthenticationFlowCompletion(completion) + self.isProcessingAuthRequest = false } ) .eraseToAnyPublisher() @@ -89,13 +89,13 @@ class AppState: ObservableObject { } func requestSMS(to trustedPhoneNumber: AuthOptionsResponse.TrustedPhoneNumber, authOptions: AuthOptionsResponse, sessionData: AppleSessionData) { - isProcessingRequest = true + isProcessingAuthRequest = true client.requestSMSSecurityCode(to: trustedPhoneNumber, authOptions: authOptions, sessionData: sessionData) .receive(on: DispatchQueue.main) .sink( receiveCompletion: { completion in self.handleAuthenticationFlowCompletion(completion) - self.isProcessingRequest = false + self.isProcessingAuthRequest = false }, receiveValue: { authenticationState in self.authenticationState = authenticationState @@ -112,13 +112,13 @@ class AppState: ObservableObject { } func submitSecurityCode(_ code: SecurityCode, sessionData: AppleSessionData) { - isProcessingRequest = true + isProcessingAuthRequest = true client.submitSecurityCode(code, sessionData: sessionData) .receive(on: DispatchQueue.main) .sink( receiveCompletion: { completion in self.handleAuthenticationFlowCompletion(completion) - self.isProcessingRequest = false + self.isProcessingAuthRequest = false }, receiveValue: { authenticationState in self.authenticationState = authenticationState