diff --git a/Xcodes/Backend/AppState.swift b/Xcodes/Backend/AppState.swift index 527ccfa..1620445 100644 --- a/Xcodes/Backend/AppState.swift +++ b/Xcodes/Backend/AppState.swift @@ -4,7 +4,7 @@ import Combine import Path import LegibleError import KeychainAccess -import SwiftUI +import Path import Version class AppState: ObservableObject { @@ -33,6 +33,7 @@ class AppState: ObservableObject { @Published var presentingSignInAlert = false @Published var isProcessingAuthRequest = false @Published var secondFactorData: SecondFactorData? + @Published var xcodeBeingConfirmedForUninstallation: Xcode? @Published var helperInstallState: HelperInstallState = .notInstalled init() { @@ -217,7 +218,7 @@ class AppState: ObservableObject { uninstallPublisher == nil else { return } - uninstallPublisher = HelperClient().uninstallXcode(installedXcode.path) + uninstallPublisher = uninstallXcode(path: installedXcode.path) .flatMap { [unowned self] _ in self.updateSelectedXcodePath() } @@ -315,6 +316,29 @@ class AppState: ObservableObject { } } + + private func uninstallXcode(path: Path) -> AnyPublisher { + let connectionErrorSubject = PassthroughSubject() + + return Deferred { + Future { promise in + do { + try Current.files.trashItem(at: path.url) + promise(.success(())) + } catch { + promise(.failure(error)) + } + } + } + // Take values, but fail when connectionErrorSubject fails + .zip( + connectionErrorSubject + .prepend("") + .map { _ in Void() } + ) + .map { $0.0 } + .eraseToAnyPublisher() + } // MARK: - Nested Types diff --git a/Xcodes/Backend/HelperClient.swift b/Xcodes/Backend/HelperClient.swift index 0c9d246..5ab5c7b 100644 --- a/Xcodes/Backend/HelperClient.swift +++ b/Xcodes/Backend/HelperClient.swift @@ -1,6 +1,5 @@ import Combine import Foundation -import Path final class HelperClient { private var connection: NSXPCConnection? @@ -104,27 +103,4 @@ final class HelperClient { .map { $0.0 } .eraseToAnyPublisher() } - - func uninstallXcode(_ path: Path) -> AnyPublisher { - let connectionErrorSubject = PassthroughSubject() - - return Deferred { - Future { promise in - do { - try Current.files.trashItem(at: path.url) - promise(.success(())) - } catch { - promise(.failure(error)) - } - } - } - // Take values, but fail when connectionErrorSubject fails - .zip( - connectionErrorSubject - .prepend("") - .map { _ in Void() } - ) - .map { $0.0 } - .eraseToAnyPublisher() - } } diff --git a/Xcodes/Backend/XcodeCommands.swift b/Xcodes/Backend/XcodeCommands.swift index d9c75e1..4cbee0b 100644 --- a/Xcodes/Backend/XcodeCommands.swift +++ b/Xcodes/Backend/XcodeCommands.swift @@ -18,6 +18,9 @@ struct XcodeCommands: Commands { OpenCommand() RevealCommand() CopyPathCommand() + + Divider() + UninstallCommand() } .environmentObject(appState) @@ -88,23 +91,14 @@ struct UninstallButton: View { @EnvironmentObject var appState: AppState let xcode: Xcode? - @State private var showingAlert = false - var alert: Alert { - Alert(title: Text("Uninstall Xcode \(xcode!.description)?"), - message: Text("It will be moved to the Trash, but won't be emptied."), - primaryButton: .destructive(Text("Uninstall"), action: { self.appState.uninstall(id: xcode!.id) }), - secondaryButton: .cancel(Text("Cancel"))) - } - var body: some View { Button(action: { - self.showingAlert = true + appState.xcodeBeingConfirmedForUninstallation = xcode }) { Text("Uninstall") } .foregroundColor(.red) .help("Uninstall") - .alert(isPresented:$showingAlert, content: { self.alert }) } } diff --git a/Xcodes/Frontend/MainWindow.swift b/Xcodes/Frontend/MainWindow.swift index fe78e1b..5052e77 100644 --- a/Xcodes/Frontend/MainWindow.swift +++ b/Xcodes/Frontend/MainWindow.swift @@ -7,13 +7,18 @@ struct MainWindow: View { @AppStorage("lastUpdated") private var lastUpdated: Double? @SceneStorage("isShowingInfoPane") private var isShowingInfoPane = false @SceneStorage("xcodeListCategory") private var category: XcodeListCategory = .all - + var body: some View { HSplitView { XcodeListView(selectedXcodeID: $selectedXcodeID, searchText: searchText, category: category) .frame(minWidth: 300) .layoutPriority(1) - + .alert(item: $appState.xcodeBeingConfirmedForUninstallation) { xcode in + Alert(title: Text("Uninstall Xcode \(xcode.description)?"), + message: Text("It will be moved to the Trash, but won't be emptied."), + primaryButton: .destructive(Text("Uninstall"), action: { self.appState.uninstall(id: xcode.id) }), + secondaryButton: .cancel(Text("Cancel"))) + } InfoPane(selectedXcodeID: selectedXcodeID) .frame(minWidth: 300, maxWidth: .infinity) .frame(width: isShowingInfoPane ? nil : 0) @@ -31,15 +36,6 @@ struct MainWindow: View { message: Text(verbatim: error.message), dismissButton: .default(Text("OK"))) } - /* - Removing this for now, because it's overriding the error alert that's being worked on above. - .alert(item: $appState.xcodeBeingConfirmedForUninstallation) { xcode in - Alert(title: Text("Uninstall Xcode \(xcode.description)?"), - message: Text("It will be moved to the Trash, but won't be emptied."), - primaryButton: .destructive(Text("Uninstall"), action: { self.appState.uninstall(id: xcode.id) }), - secondaryButton: .cancel(Text("Cancel"))) - } - **/ .sheet(isPresented: $appState.secondFactorData.isNotNil) { secondFactorView(appState.secondFactorData!) .environmentObject(appState) diff --git a/Xcodes/Frontend/XcodeList/InfoPane.swift b/Xcodes/Frontend/XcodeList/InfoPane.swift index 15bfd24..0c2f29b 100644 --- a/Xcodes/Frontend/XcodeList/InfoPane.swift +++ b/Xcodes/Frontend/XcodeList/InfoPane.swift @@ -37,6 +37,9 @@ struct InfoPane: View { OpenButton(xcode: xcode) .help("Open") + + Spacer() + UninstallButton(xcode: xcode) } } else { InstallButton(xcode: xcode) @@ -49,12 +52,7 @@ struct InfoPane: View { compatibility(for: xcode) sdks(for: xcode) compilers(for: xcode) - - if xcode.path != nil { - VStack(alignment: .leading) { - UninstallButton(xcode: xcode) - } - } + Spacer() } } else {