mirror of
https://github.com/XcodesOrg/XcodesApp.git
synced 2026-04-25 14:47:38 +00:00
Merge pull request #23 from RobotsAndPencils/enhancement/15-DisableButtonWhenSending
Enhancement/15 disable button when sending SMS codes et al
This commit is contained in:
commit
e3687eacc6
7 changed files with 113 additions and 18 deletions
|
|
@ -7,6 +7,7 @@
|
||||||
objects = {
|
objects = {
|
||||||
|
|
||||||
/* Begin PBXBuildFile section */
|
/* 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 */; };
|
CA378F992466567600A58CE0 /* AppState.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA378F982466567600A58CE0 /* AppState.swift */; };
|
||||||
CA39711924495F0E00AFFB77 /* AppStoreButtonStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA39711824495F0E00AFFB77 /* AppStoreButtonStyle.swift */; };
|
CA39711924495F0E00AFFB77 /* AppStoreButtonStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA39711824495F0E00AFFB77 /* AppStoreButtonStyle.swift */; };
|
||||||
CA44901F2463AD34003D8213 /* Tag.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA44901E2463AD34003D8213 /* Tag.swift */; };
|
CA44901F2463AD34003D8213 /* Tag.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA44901E2463AD34003D8213 /* Tag.swift */; };
|
||||||
|
|
@ -64,6 +65,7 @@
|
||||||
/* End PBXContainerItemProxy section */
|
/* End PBXContainerItemProxy section */
|
||||||
|
|
||||||
/* Begin PBXFileReference section */
|
/* Begin PBXFileReference section */
|
||||||
|
63EAA4EA259944450046AB8F /* ProgressButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProgressButton.swift; sourceTree = "<group>"; };
|
||||||
CA378F982466567600A58CE0 /* AppState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppState.swift; sourceTree = "<group>"; };
|
CA378F982466567600A58CE0 /* AppState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppState.swift; sourceTree = "<group>"; };
|
||||||
CA39711824495F0E00AFFB77 /* AppStoreButtonStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppStoreButtonStyle.swift; sourceTree = "<group>"; };
|
CA39711824495F0E00AFFB77 /* AppStoreButtonStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppStoreButtonStyle.swift; sourceTree = "<group>"; };
|
||||||
CA44901E2463AD34003D8213 /* Tag.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Tag.swift; sourceTree = "<group>"; };
|
CA44901E2463AD34003D8213 /* Tag.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Tag.swift; sourceTree = "<group>"; };
|
||||||
|
|
@ -142,6 +144,14 @@
|
||||||
/* End PBXFrameworksBuildPhase section */
|
/* End PBXFrameworksBuildPhase section */
|
||||||
|
|
||||||
/* Begin PBXGroup section */
|
/* Begin PBXGroup section */
|
||||||
|
63EAA4E9259944340046AB8F /* Common */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
63EAA4EA259944450046AB8F /* ProgressButton.swift */,
|
||||||
|
);
|
||||||
|
path = Common;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
CA538A12255A4F7C00E64DD7 /* Frameworks */ = {
|
CA538A12255A4F7C00E64DD7 /* Frameworks */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
|
@ -164,9 +174,9 @@
|
||||||
children = (
|
children = (
|
||||||
CA735108257BF96D00EA9CF8 /* AttributedText.swift */,
|
CA735108257BF96D00EA9CF8 /* AttributedText.swift */,
|
||||||
CA73510C257BFCEF00EA9CF8 /* NSAttributedString+.swift */,
|
CA73510C257BFCEF00EA9CF8 /* NSAttributedString+.swift */,
|
||||||
|
CA5D781D257365D6008EDE9D /* PinCodeTextView.swift */,
|
||||||
CAA1CB34255A5AD5003FD669 /* SignInCredentialsView.swift */,
|
CAA1CB34255A5AD5003FD669 /* SignInCredentialsView.swift */,
|
||||||
CAA1CB44255A5B60003FD669 /* SignIn2FAView.swift */,
|
CAA1CB44255A5B60003FD669 /* SignIn2FAView.swift */,
|
||||||
CA5D781D257365D6008EDE9D /* PinCodeTextView.swift */,
|
|
||||||
CAA1CB48255A5C97003FD669 /* SignInSMSView.swift */,
|
CAA1CB48255A5C97003FD669 /* SignInSMSView.swift */,
|
||||||
CAA1CB4C255A5CFD003FD669 /* SignInPhoneListView.swift */,
|
CAA1CB4C255A5CFD003FD669 /* SignInPhoneListView.swift */,
|
||||||
);
|
);
|
||||||
|
|
@ -210,6 +220,7 @@
|
||||||
CABFAA1A2592F7D900380FEE /* Frontend */ = {
|
CABFAA1A2592F7D900380FEE /* Frontend */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
63EAA4E9259944340046AB8F /* Common */,
|
||||||
CA9FF8552595082000E47BAF /* About */,
|
CA9FF8552595082000E47BAF /* About */,
|
||||||
CAA1CB50255A5D16003FD669 /* SignIn */,
|
CAA1CB50255A5D16003FD669 /* SignIn */,
|
||||||
CABFAA142592F73000380FEE /* XcodeList */,
|
CABFAA142592F73000380FEE /* XcodeList */,
|
||||||
|
|
@ -458,6 +469,7 @@
|
||||||
CABFAA432593104F00380FEE /* AboutView.swift in Sources */,
|
CABFAA432593104F00380FEE /* AboutView.swift in Sources */,
|
||||||
CABFA9CC2592EEEA00380FEE /* Path+.swift in Sources */,
|
CABFA9CC2592EEEA00380FEE /* Path+.swift in Sources */,
|
||||||
CAD2E7A22449574E00113D76 /* XcodesApp.swift in Sources */,
|
CAD2E7A22449574E00113D76 /* XcodesApp.swift in Sources */,
|
||||||
|
63EAA4EB259944450046AB8F /* ProgressButton.swift in Sources */,
|
||||||
CA5D781E257365D6008EDE9D /* PinCodeTextView.swift in Sources */,
|
CA5D781E257365D6008EDE9D /* PinCodeTextView.swift in Sources */,
|
||||||
CA39711924495F0E00AFFB77 /* AppStoreButtonStyle.swift in Sources */,
|
CA39711924495F0E00AFFB77 /* AppStoreButtonStyle.swift in Sources */,
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,9 @@ class AppState: ObservableObject {
|
||||||
@Published var authenticationState: AuthenticationState = .unauthenticated
|
@Published var authenticationState: AuthenticationState = .unauthenticated
|
||||||
@Published var allVersions: [XcodeVersion] = []
|
@Published var allVersions: [XcodeVersion] = []
|
||||||
@Published var error: AlertContent?
|
@Published var error: AlertContent?
|
||||||
|
@Published var authError: AlertContent?
|
||||||
@Published var presentingSignInAlert = false
|
@Published var presentingSignInAlert = false
|
||||||
|
@Published var isProcessingAuthRequest = false
|
||||||
@Published var secondFactorData: SecondFactorData?
|
@Published var secondFactorData: SecondFactorData?
|
||||||
|
|
||||||
// MARK: - Authentication
|
// MARK: - Authentication
|
||||||
|
|
@ -62,6 +64,7 @@ class AppState: ObservableObject {
|
||||||
try? Current.keychain.set(password, key: username)
|
try? Current.keychain.set(password, key: username)
|
||||||
Current.defaults.set(username, forKey: "username")
|
Current.defaults.set(username, forKey: "username")
|
||||||
|
|
||||||
|
isProcessingAuthRequest = true
|
||||||
return client.login(accountName: username, password: password)
|
return client.login(accountName: username, password: password)
|
||||||
.receive(on: DispatchQueue.main)
|
.receive(on: DispatchQueue.main)
|
||||||
.handleEvents(
|
.handleEvents(
|
||||||
|
|
@ -70,6 +73,7 @@ class AppState: ObservableObject {
|
||||||
},
|
},
|
||||||
receiveCompletion: { completion in
|
receiveCompletion: { completion in
|
||||||
self.handleAuthenticationFlowCompletion(completion)
|
self.handleAuthenticationFlowCompletion(completion)
|
||||||
|
self.isProcessingAuthRequest = false
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
.eraseToAnyPublisher()
|
.eraseToAnyPublisher()
|
||||||
|
|
@ -85,10 +89,13 @@ class AppState: ObservableObject {
|
||||||
}
|
}
|
||||||
|
|
||||||
func requestSMS(to trustedPhoneNumber: AuthOptionsResponse.TrustedPhoneNumber, authOptions: AuthOptionsResponse, sessionData: AppleSessionData) {
|
func requestSMS(to trustedPhoneNumber: AuthOptionsResponse.TrustedPhoneNumber, authOptions: AuthOptionsResponse, sessionData: AppleSessionData) {
|
||||||
|
isProcessingAuthRequest = true
|
||||||
client.requestSMSSecurityCode(to: trustedPhoneNumber, authOptions: authOptions, sessionData: sessionData)
|
client.requestSMSSecurityCode(to: trustedPhoneNumber, authOptions: authOptions, sessionData: sessionData)
|
||||||
|
.receive(on: DispatchQueue.main)
|
||||||
.sink(
|
.sink(
|
||||||
receiveCompletion: { completion in
|
receiveCompletion: { completion in
|
||||||
self.handleAuthenticationFlowCompletion(completion)
|
self.handleAuthenticationFlowCompletion(completion)
|
||||||
|
self.isProcessingAuthRequest = false
|
||||||
},
|
},
|
||||||
receiveValue: { authenticationState in
|
receiveValue: { authenticationState in
|
||||||
self.authenticationState = authenticationState
|
self.authenticationState = authenticationState
|
||||||
|
|
@ -105,11 +112,13 @@ class AppState: ObservableObject {
|
||||||
}
|
}
|
||||||
|
|
||||||
func submitSecurityCode(_ code: SecurityCode, sessionData: AppleSessionData) {
|
func submitSecurityCode(_ code: SecurityCode, sessionData: AppleSessionData) {
|
||||||
|
isProcessingAuthRequest = true
|
||||||
client.submitSecurityCode(code, sessionData: sessionData)
|
client.submitSecurityCode(code, sessionData: sessionData)
|
||||||
.receive(on: DispatchQueue.main)
|
.receive(on: DispatchQueue.main)
|
||||||
.sink(
|
.sink(
|
||||||
receiveCompletion: { completion in
|
receiveCompletion: { completion in
|
||||||
self.handleAuthenticationFlowCompletion(completion)
|
self.handleAuthenticationFlowCompletion(completion)
|
||||||
|
self.isProcessingAuthRequest = false
|
||||||
},
|
},
|
||||||
receiveValue: { authenticationState in
|
receiveValue: { authenticationState in
|
||||||
self.authenticationState = authenticationState
|
self.authenticationState = authenticationState
|
||||||
|
|
@ -126,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.
|
// 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)
|
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:
|
case .finished:
|
||||||
switch self.authenticationState {
|
switch self.authenticationState {
|
||||||
case .authenticated, .unauthenticated:
|
case .authenticated, .unauthenticated:
|
||||||
|
|
|
||||||
34
Xcodes/Frontend/Common/ProgressButton.swift
Normal file
34
Xcodes/Frontend/Common/ProgressButton.swift
Normal file
|
|
@ -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<Label: View>: 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -24,12 +24,21 @@ struct SignIn2FAView: View {
|
||||||
.keyboardShortcut(.cancelAction)
|
.keyboardShortcut(.cancelAction)
|
||||||
Button("Send SMS", action: { appState.choosePhoneNumberForSMS(authOptions: authOptions, sessionData: sessionData) })
|
Button("Send SMS", action: { appState.choosePhoneNumberForSMS(authOptions: authOptions, sessionData: sessionData) })
|
||||||
Spacer()
|
Spacer()
|
||||||
Button("Continue", action: { appState.submitSecurityCode(.device(code: code), sessionData: sessionData) })
|
ProgressButton(isInProgress: appState.isProcessingAuthRequest,
|
||||||
.keyboardShortcut(.defaultAction)
|
action: { appState.submitSecurityCode(.device(code: code), sessionData: sessionData) }) {
|
||||||
.disabled(code.count != authOptions.securityCode.length)
|
Text("Continue")
|
||||||
|
}
|
||||||
|
.keyboardShortcut(.defaultAction)
|
||||||
|
.disabled(code.count != authOptions.securityCode.length)
|
||||||
}
|
}
|
||||||
|
.frame(height: 25)
|
||||||
}
|
}
|
||||||
.padding()
|
.padding()
|
||||||
|
.alert(item: $appState.authError) { error in
|
||||||
|
Alert(title: Text(error.title),
|
||||||
|
message: Text(verbatim: error.message),
|
||||||
|
dismissButton: .default(Text("OK")))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -28,12 +28,21 @@ struct SignInCredentialsView: View {
|
||||||
Spacer()
|
Spacer()
|
||||||
Button("Cancel") { isPresented = false }
|
Button("Cancel") { isPresented = false }
|
||||||
.keyboardShortcut(.cancelAction)
|
.keyboardShortcut(.cancelAction)
|
||||||
Button("Next") { appState.signIn(username: username, password: password) }
|
ProgressButton(isInProgress: appState.isProcessingAuthRequest,
|
||||||
.disabled(username.isEmpty)
|
action: { appState.signIn(username: username, password: password) }) {
|
||||||
.keyboardShortcut(.defaultAction)
|
Text("Next")
|
||||||
|
}
|
||||||
|
.disabled(username.isEmpty || password.isEmpty)
|
||||||
|
.keyboardShortcut(.defaultAction)
|
||||||
}
|
}
|
||||||
|
.frame(height: 25)
|
||||||
}
|
}
|
||||||
.padding()
|
.padding()
|
||||||
|
.alert(item: $appState.authError) { error in
|
||||||
|
Alert(title: Text(error.title),
|
||||||
|
message: Text(verbatim: error.message),
|
||||||
|
dismissButton: .default(Text("OK")))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -16,24 +16,34 @@ struct SignInPhoneListView: View {
|
||||||
List(phoneNumbers, selection: $selectedPhoneNumberID) {
|
List(phoneNumbers, selection: $selectedPhoneNumberID) {
|
||||||
Text($0.numberWithDialCode)
|
Text($0.numberWithDialCode)
|
||||||
}
|
}
|
||||||
.frame(height: 200)
|
|
||||||
} else {
|
} else {
|
||||||
AttributedText(
|
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()
|
.convertingURLsToLinkAttributes()
|
||||||
)
|
)
|
||||||
|
Spacer()
|
||||||
}
|
}
|
||||||
|
|
||||||
HStack {
|
HStack {
|
||||||
Button("Cancel", action: { isPresented = false })
|
Button("Cancel", action: { isPresented = false })
|
||||||
.keyboardShortcut(.cancelAction)
|
.keyboardShortcut(.cancelAction)
|
||||||
Spacer()
|
Spacer()
|
||||||
Button("Continue", action: { appState.requestSMS(to: authOptions.trustedPhoneNumbers!.first { $0.id == selectedPhoneNumberID }!, authOptions: authOptions, sessionData: sessionData) })
|
ProgressButton(isInProgress: appState.isProcessingAuthRequest,
|
||||||
.keyboardShortcut(.defaultAction)
|
action: { appState.requestSMS(to: authOptions.trustedPhoneNumbers!.first { $0.id == selectedPhoneNumberID }!, authOptions: authOptions, sessionData: sessionData) }) {
|
||||||
.disabled(selectedPhoneNumberID == nil)
|
Text("Continue")
|
||||||
|
}
|
||||||
|
.keyboardShortcut(.defaultAction)
|
||||||
|
.disabled(selectedPhoneNumberID == nil)
|
||||||
}
|
}
|
||||||
|
.frame(height: 25)
|
||||||
}
|
}
|
||||||
.padding()
|
.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")))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -48,7 +58,8 @@ struct SignInPhoneListView_Previews: PreviewProvider {
|
||||||
securityCode: .init(length: 6)),
|
securityCode: .init(length: 6)),
|
||||||
sessionData: AppleSessionData(serviceKey: "", sessionID: "", scnt: "")
|
sessionData: AppleSessionData(serviceKey: "", sessionID: "", scnt: "")
|
||||||
)
|
)
|
||||||
|
.environmentObject(AppState())
|
||||||
|
|
||||||
SignInPhoneListView(
|
SignInPhoneListView(
|
||||||
isPresented: .constant(true),
|
isPresented: .constant(true),
|
||||||
authOptions: AuthOptionsResponse(
|
authOptions: AuthOptionsResponse(
|
||||||
|
|
@ -57,6 +68,7 @@ struct SignInPhoneListView_Previews: PreviewProvider {
|
||||||
securityCode: .init(length: 6)),
|
securityCode: .init(length: 6)),
|
||||||
sessionData: AppleSessionData(serviceKey: "", sessionID: "", scnt: "")
|
sessionData: AppleSessionData(serviceKey: "", sessionID: "", scnt: "")
|
||||||
)
|
)
|
||||||
|
.environmentObject(AppState())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -24,12 +24,21 @@ struct SignInSMSView: View {
|
||||||
Button("Cancel", action: { isPresented = false })
|
Button("Cancel", action: { isPresented = false })
|
||||||
.keyboardShortcut(.cancelAction)
|
.keyboardShortcut(.cancelAction)
|
||||||
Spacer()
|
Spacer()
|
||||||
Button("Continue", action: { appState.submitSecurityCode(.sms(code: code, phoneNumberId: trustedPhoneNumber.id), sessionData: sessionData) })
|
ProgressButton(isInProgress: appState.isProcessingAuthRequest,
|
||||||
.keyboardShortcut(.defaultAction)
|
action: { appState.submitSecurityCode(.sms(code: code, phoneNumberId: trustedPhoneNumber.id), sessionData: sessionData) }) {
|
||||||
.disabled(code.count != authOptions.securityCode.length)
|
Text("Continue")
|
||||||
|
}
|
||||||
|
.keyboardShortcut(.defaultAction)
|
||||||
|
.disabled(code.count != authOptions.securityCode.length)
|
||||||
}
|
}
|
||||||
|
.frame(height: 25)
|
||||||
}
|
}
|
||||||
.padding()
|
.padding()
|
||||||
|
.alert(item: $appState.authError) { error in
|
||||||
|
Alert(title: Text(error.title),
|
||||||
|
message: Text(verbatim: error.message),
|
||||||
|
dismissButton: .default(Text("OK")))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue