diff --git a/Xcodes.xcodeproj/project.pbxproj b/Xcodes.xcodeproj/project.pbxproj index 85fea2d..cdada1e 100644 --- a/Xcodes.xcodeproj/project.pbxproj +++ b/Xcodes.xcodeproj/project.pbxproj @@ -17,6 +17,7 @@ CA9FF83F2594FBC000E47BAF /* Licenses.rtf in Resources */ = {isa = PBXBuildFile; fileRef = CA9FF83E2594FBC000E47BAF /* Licenses.rtf */; }; CA9FF84E2595079F00E47BAF /* ScrollingTextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA9FF84D2595079F00E47BAF /* ScrollingTextView.swift */; }; CA9FF8522595080100E47BAF /* AcknowledgementsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA9FF8512595080100E47BAF /* AcknowledgementsView.swift */; }; + CA9FF8662595130600E47BAF /* View+IsHidden.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA9FF8652595130600E47BAF /* View+IsHidden.swift */; }; CAA1CB2D255A5262003FD669 /* AppleAPI in Frameworks */ = {isa = PBXBuildFile; productRef = CAA1CB2C255A5262003FD669 /* AppleAPI */; }; CAA1CB35255A5AD5003FD669 /* SignInCredentialsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CAA1CB34255A5AD5003FD669 /* SignInCredentialsView.swift */; }; CAA1CB45255A5B60003FD669 /* SignIn2FAView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CAA1CB44255A5B60003FD669 /* SignIn2FAView.swift */; }; @@ -80,6 +81,7 @@ CA9FF83E2594FBC000E47BAF /* Licenses.rtf */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.rtf; path = Licenses.rtf; sourceTree = ""; }; CA9FF84D2595079F00E47BAF /* ScrollingTextView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScrollingTextView.swift; sourceTree = ""; }; CA9FF8512595080100E47BAF /* AcknowledgementsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AcknowledgementsView.swift; sourceTree = ""; }; + CA9FF8652595130600E47BAF /* View+IsHidden.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "View+IsHidden.swift"; sourceTree = ""; }; CAA1CB34255A5AD5003FD669 /* SignInCredentialsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignInCredentialsView.swift; sourceTree = ""; }; CAA1CB44255A5B60003FD669 /* SignIn2FAView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignIn2FAView.swift; sourceTree = ""; }; CAA1CB48255A5C97003FD669 /* SignInSMSView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignInSMSView.swift; sourceTree = ""; }; @@ -225,6 +227,7 @@ CAA1CB50255A5D16003FD669 /* SignIn */, CABFAA142592F73000380FEE /* XcodeList */, CABFAA2A2592FBFC00380FEE /* SettingsView.swift */, + CA9FF8652595130600E47BAF /* View+IsHidden.swift */, ); path = Frontend; sourceTree = ""; @@ -441,6 +444,7 @@ files = ( CA735109257BF96D00EA9CF8 /* AttributedText.swift in Sources */, CABFAA492593162500380FEE /* Bundle+InfoPlistValues.swift in Sources */, + CA9FF8662595130600E47BAF /* View+IsHidden.swift in Sources */, CABFA9CA2592EEEA00380FEE /* XcodeList.swift in Sources */, CA44901F2463AD34003D8213 /* Tag.swift in Sources */, CABFA9BF2592EEEA00380FEE /* URLSession+Promise.swift in Sources */, diff --git a/Xcodes/Backend/AppState.swift b/Xcodes/Backend/AppState.swift index 3aac23c..68dec29 100644 --- a/Xcodes/Backend/AppState.swift +++ b/Xcodes/Backend/AppState.swift @@ -12,6 +12,8 @@ class AppState: ObservableObject { @Published var authenticationState: AuthenticationState = .unauthenticated @Published var allVersions: [XcodeVersion] = [] + @Published var updatePublisher: AnyCancellable? + var isUpdating: Bool { updatePublisher != nil } @Published var error: AlertContent? @Published var authError: AlertContent? @Published var presentingSignInAlert = false @@ -161,15 +163,17 @@ class AppState: ObservableObject { // MARK: - Load Xcode Versions func update() { - update() + guard !isUpdating else { return } + updatePublisher = update() .sink( - receiveCompletion: { _ in }, + receiveCompletion: { [unowned self] _ in + self.updatePublisher = nil + }, receiveValue: { _ in } ) - .store(in: &cancellables) } - public func update() -> AnyPublisher<[Xcode], Never> { + private func update() -> AnyPublisher<[Xcode], Never> { signInIfNeeded() .flatMap { self.list.update() diff --git a/Xcodes/Frontend/View+IsHidden.swift b/Xcodes/Frontend/View+IsHidden.swift new file mode 100644 index 0000000..4da6da0 --- /dev/null +++ b/Xcodes/Frontend/View+IsHidden.swift @@ -0,0 +1,24 @@ +import SwiftUI + +extension View { + @ViewBuilder + func isHidden(_ isHidden: Bool) -> some View { + if isHidden { + self.hidden() + } else { + self + } + } +} + +struct View_IsHidden_Previews: PreviewProvider { + static var previews: some View { + Group { + Text("Not Hidden") + .isHidden(false) + + Text("Hidden") + .isHidden(true) + } + } +} diff --git a/Xcodes/Frontend/XcodeList/XcodeListView.swift b/Xcodes/Frontend/XcodeList/XcodeListView.swift index 7a2d0e5..7b70958 100644 --- a/Xcodes/Frontend/XcodeList/XcodeListView.swift +++ b/Xcodes/Frontend/XcodeList/XcodeListView.swift @@ -79,9 +79,16 @@ struct XcodeListView: View { .toolbar { ToolbarItemGroup(placement: .primaryAction) { Button(action: appState.update) { - Image(systemName: "arrow.clockwise") + Label("Refresh", systemImage: "arrow.clockwise") } .keyboardShortcut(KeyEquivalent("r")) + .disabled(appState.isUpdating) + .isHidden(appState.isUpdating) + .overlay( + ProgressView() + .scaleEffect(0.5, anchor: .center) + .isHidden(!appState.isUpdating) + ) } ToolbarItem(placement: .principal) { Picker("", selection: $category) { diff --git a/Xcodes/XcodesApp.swift b/Xcodes/XcodesApp.swift index 88645fa..0a46137 100644 --- a/Xcodes/XcodesApp.swift +++ b/Xcodes/XcodesApp.swift @@ -17,6 +17,13 @@ struct XcodesApp: App { appDelegate.showAboutWindow() } } + CommandGroup(after: CommandGroupPlacement.newItem) { + Button("Refresh") { + appState.update() + } + .keyboardShortcut(KeyEquivalent("r")) + .disabled(appState.isUpdating) + } } Settings {