diff --git a/Xcodes.xcodeproj/project.pbxproj b/Xcodes.xcodeproj/project.pbxproj index 6c418b8..a1ee749 100644 --- a/Xcodes.xcodeproj/project.pbxproj +++ b/Xcodes.xcodeproj/project.pbxproj @@ -114,8 +114,8 @@ E689540325BE8C64000EBCEA /* DockProgress in Frameworks */ = {isa = PBXBuildFile; productRef = E689540225BE8C64000EBCEA /* DockProgress */; }; E81D7EA02805250100A205FC /* Collection+.swift in Sources */ = {isa = PBXBuildFile; fileRef = E81D7E9F2805250100A205FC /* Collection+.swift */; }; E832EAF82B0FBCF4001B570D /* RuntimeInstallationStepDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E832EAF72B0FBCF4001B570D /* RuntimeInstallationStepDetailView.swift */; }; + E84B7D0D2B296A8900DBDA2B /* NavigationSplitViewWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = E84B7D0C2B296A8900DBDA2B /* NavigationSplitViewWrapper.swift */; }; E84CF8C12B0FEB8300ECA259 /* RuntimesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E84CF8C02B0FEB8300ECA259 /* RuntimesView.swift */; }; - E872EE4E2808D4F100D3DD8B /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = E872EE502808D4F100D3DD8B /* Localizable.strings */; }; E87AB3C52939B65E00D72F43 /* Hardware.swift in Sources */ = {isa = PBXBuildFile; fileRef = E87AB3C42939B65E00D72F43 /* Hardware.swift */; }; E87DD6EB25D053FA00D86808 /* Progress+.swift in Sources */ = {isa = PBXBuildFile; fileRef = E87DD6EA25D053FA00D86808 /* Progress+.swift */; }; E89342FA25EDCC17007CF557 /* NotificationManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = E89342F925EDCC17007CF557 /* NotificationManager.swift */; }; @@ -310,9 +310,9 @@ CAFFFEEE259CEAC400903F81 /* RingProgressViewStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RingProgressViewStyle.swift; sourceTree = ""; }; E81D7E9F2805250100A205FC /* Collection+.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Collection+.swift"; sourceTree = ""; }; E832EAF72B0FBCF4001B570D /* RuntimeInstallationStepDetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RuntimeInstallationStepDetailView.swift; sourceTree = ""; }; + E84B7D0C2B296A8900DBDA2B /* NavigationSplitViewWrapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationSplitViewWrapper.swift; sourceTree = ""; }; E84CF8C02B0FEB8300ECA259 /* RuntimesView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RuntimesView.swift; sourceTree = ""; }; E856BB73291EDD3D00DC438B /* XcodesKit */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = XcodesKit; path = Xcodes/XcodesKit; sourceTree = ""; }; - E872EE4F2808D4F100D3DD8B /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = ""; }; E87AB3C42939B65E00D72F43 /* Hardware.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Hardware.swift; sourceTree = ""; }; E87DD6EA25D053FA00D86808 /* Progress+.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Progress+.swift"; sourceTree = ""; }; E89342F925EDCC17007CF557 /* NotificationManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationManager.swift; sourceTree = ""; }; @@ -373,6 +373,7 @@ CA452BAF259FD9770072DFA4 /* ProgressIndicator.swift */, 536CFDD3263C9A8000026CE0 /* XcodesSheet.swift */, 53CBAB2B263DCC9100410495 /* XcodesAlert.swift */, + E84B7D0C2B296A8900DBDA2B /* NavigationSplitViewWrapper.swift */, ); path = Common; sourceTree = ""; @@ -921,6 +922,7 @@ E8B20CBF2A2EDEC20057D816 /* SDKs+Xcode.swift in Sources */, CA9FF877259528CC00E47BAF /* Version+XcodeReleases.swift in Sources */, CABFAA2D2592FBFC00380FEE /* Configure.swift in Sources */, + E84B7D0D2B296A8900DBDA2B /* NavigationSplitViewWrapper.swift in Sources */, CA73510D257BFCEF00EA9CF8 /* NSAttributedString+.swift in Sources */, CAFBDB952598FE96003DCC5A /* FocusedValues.swift in Sources */, B0403CF42AD9381D00137C09 /* SDKsView.swift in Sources */, @@ -1040,7 +1042,7 @@ GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; LOCALIZATION_PREFERS_STRING_CATALOGS = YES; - MACOSX_DEPLOYMENT_TARGET = 11.0; + MACOSX_DEPLOYMENT_TARGET = 12.0; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; ONLY_ACTIVE_ARCH = YES; @@ -1227,7 +1229,7 @@ GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; LOCALIZATION_PREFERS_STRING_CATALOGS = YES; - MACOSX_DEPLOYMENT_TARGET = 11.0; + MACOSX_DEPLOYMENT_TARGET = 12.0; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; ONLY_ACTIVE_ARCH = YES; @@ -1285,7 +1287,7 @@ GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; LOCALIZATION_PREFERS_STRING_CATALOGS = YES; - MACOSX_DEPLOYMENT_TARGET = 11.0; + MACOSX_DEPLOYMENT_TARGET = 12.0; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; SDKROOT = macosx; diff --git a/Xcodes/Frontend/Common/NavigationSplitViewWrapper.swift b/Xcodes/Frontend/Common/NavigationSplitViewWrapper.swift new file mode 100644 index 0000000..928e49a --- /dev/null +++ b/Xcodes/Frontend/Common/NavigationSplitViewWrapper.swift @@ -0,0 +1,46 @@ +// +// NavigationSplitViewWrapper.swift +// Xcodes +// +// Created by Matt Kiazyk on 2023-12-12. +// + +import SwiftUI + +struct NavigationSplitViewWrapper: View where Sidebar: View, Detail: View { + private var sidebar: Sidebar + private var detail: Detail + + init( + @ViewBuilder sidebar: () -> Sidebar, + @ViewBuilder detail: () -> Detail + ) { + self.sidebar = sidebar() + self.detail = detail() + } + + var body: some View { + if #available(iOS 16, macOS 13, tvOS 16, watchOS 9, visionOS 1, *) { + // Use the latest API available + NavigationSplitView { + + if #available(macOS 14, *) { + sidebar + .toolbar(removing: .sidebarToggle) + } else { + sidebar + } + } detail: { + detail + } + } else { + // Alternative code for earlier versions of OS. + NavigationView { + // The first column is the sidebar. + sidebar + detail + } + .navigationViewStyle(.columns) + } + } +} diff --git a/Xcodes/Frontend/MainWindow.swift b/Xcodes/Frontend/MainWindow.swift index b8df774..406d2a6 100644 --- a/Xcodes/Frontend/MainWindow.swift +++ b/Xcodes/Frontend/MainWindow.swift @@ -1,6 +1,8 @@ import ErrorHandling import SwiftUI import XcodesKit +import Path +import Version struct MainWindow: View { @EnvironmentObject var appState: AppState @@ -16,7 +18,7 @@ struct MainWindow: View { @AppStorage("isInstalledOnly") private var isInstalledOnly = false var body: some View { - HSplitView { + NavigationSplitViewWrapper { XcodeListView(selectedXcodeID: $selectedXcodeID, searchText: searchText, category: category, isInstalledOnly: isInstalledOnly) .frame(minWidth: 300) .layoutPriority(1) @@ -26,25 +28,73 @@ struct MainWindow: View { primaryButton: .destructive(Text("Uninstall"), action: { self.appState.uninstall(xcode: xcode) }), secondaryButton: .cancel(Text("Cancel"))) } - - if isShowingInfoPane { - Group { - if let xcode = xcode { - InfoPane(xcode: xcode) + .searchable(text: $searchText, placement: .sidebar) + .mainToolbar( + category: $category, + isInstalledOnly: $isInstalledOnly, + isShowingInfoPane: $isShowingInfoPane + ) + } detail: { + Group { + if let xcode = xcode { + InfoPane(xcode: xcode) + } else { + UnselectedView() + } + } + .padding() + .toolbar { + ToolbarItemGroup { + Button(action: { appState.presentedSheet = .signIn }, label: { + Label("Login", systemImage: "person.circle") + }) + .help("LoginDescription") + if #available(macOS 14, *) { + SettingsLink(label: { + Label("Preferences", systemImage: "gearshape") + }) + .help("PreferencesDescription") } else { - UnselectedView() + Button(action: { + if #available(macOS 13, *) { + NSApp.sendAction(Selector(("showSettingsWindow:")), to: nil, from: nil) + } else { + NSApp.sendAction(Selector(("showPreferencesWindow:")), to: nil, from: nil) + } + }, label: { + Label("Preferences", systemImage: "gearshape") + }) + .help("PreferencesDescription") } } - .padding() - .frame(minWidth: 300, maxWidth: .infinity) } } - .mainToolbar( - category: $category, - isInstalledOnly: $isInstalledOnly, - isShowingInfoPane: $isShowingInfoPane, - searchText: $searchText - ) + +// HSplitView { +// XcodeListView(selectedXcodeID: $selectedXcodeID, searchText: searchText, category: category, isInstalledOnly: isInstalledOnly) +// .frame(minWidth: 300) +// .layoutPriority(1) +// .alert(item: $appState.xcodeBeingConfirmedForUninstallation) { xcode in +// Alert(title: Text(String(format: localizeString("Alert.Uninstall.Title"), xcode.description)), +// message: Text("Alert.Uninstall.Message"), +// primaryButton: .destructive(Text("Uninstall"), action: { self.appState.uninstall(xcode: xcode) }), +// secondaryButton: .cancel(Text("Cancel"))) +// } +// .searchable(text: $searchText) +// +// if isShowingInfoPane { +// Group { +// if let xcode = xcode { +// InfoPane(xcode: xcode) +// } else { +// UnselectedView() +// } +// } +// .padding() +// .frame(minWidth: 300, maxWidth: .infinity) +// } +// } + .bottomStatusBar() .padding([.top], 0) .navigationSubtitle(subtitleText) @@ -197,6 +247,16 @@ struct MainWindow: View { struct MainWindow_Previews: PreviewProvider { static var previews: some View { - MainWindow() + MainWindow().environmentObject({ () -> AppState in + let a = AppState() + a.allXcodes = [ + Xcode(version: Version("12.0.0+1234A")!, identicalBuilds: [Version("12.0.0+1234A")!, Version("12.0.0-RC+1234A")!], installState: .installed(Path("/Applications/Xcode-12.3.0.app")!), selected: false, icon: nil), + Xcode(version: Version("12.3.0")!, installState: .installed(Path("/Applications/Xcode-12.3.0.app")!), selected: true, icon: nil), + Xcode(version: Version("12.2.0")!, installState: .notInstalled, selected: false, icon: nil), + Xcode(version: Version("12.1.0")!, installState: .installing(.downloading(progress: configure(Progress(totalUnitCount: 100)) { $0.completedUnitCount = 40 })), selected: false, icon: nil), + Xcode(version: Version("12.0.0")!, installState: .installed(Path("/Applications/Xcode-12.3.0.app")!), selected: false, icon: nil), + ] + return a + }()) } } diff --git a/Xcodes/Frontend/XcodeList/MainToolbar.swift b/Xcodes/Frontend/XcodeList/MainToolbar.swift index e355304..b21460b 100644 --- a/Xcodes/Frontend/XcodeList/MainToolbar.swift +++ b/Xcodes/Frontend/XcodeList/MainToolbar.swift @@ -5,7 +5,6 @@ struct MainToolbarModifier: ViewModifier { @Binding var category: XcodeListCategory @Binding var isInstalledOnly: Bool @Binding var isShowingInfoPane: Bool - @Binding var searchText: String func body(content: Content) -> some View { content @@ -14,10 +13,6 @@ struct MainToolbarModifier: ViewModifier { private var toolbar: some ToolbarContent { ToolbarItemGroup { - Button(action: { appState.presentedSheet = .signIn }, label: { - Label("Login", systemImage: "person.circle") - }) - .help("LoginDescription") ProgressButton( isInProgress: appState.isUpdating, @@ -27,7 +22,7 @@ struct MainToolbarModifier: ViewModifier { } .keyboardShortcut(KeyEquivalent("r")) .help("RefreshDescription") - + Spacer() Button(action: { switch category { case .all: category = .release @@ -75,39 +70,18 @@ struct MainToolbarModifier: ViewModifier { } .help("FilterInstalledDescription") - Button(action: { isShowingInfoPane.toggle() }) { - if isShowingInfoPane { - Label("Info", systemImage: "info.circle.fill") - .foregroundColor(.accentColor) - } else { - Label("Info", systemImage: "info.circle") - } - } - .keyboardShortcut(KeyboardShortcut("i", modifiers: [.command, .option])) - .help("InfoDescription") +// Button(action: { isShowingInfoPane.toggle() }) { +// if isShowingInfoPane { +// Label("Info", systemImage: "info.circle.fill") +// .foregroundColor(.accentColor) +// } else { +// Label("Info", systemImage: "info.circle") +// } +// } +// .keyboardShortcut(KeyboardShortcut("i", modifiers: [.command, .option])) +// .help("InfoDescription") - if #available(macOS 14, *) { - SettingsLink(label: { - Label("Preferences", systemImage: "gearshape") - }) - .help("PreferencesDescription") - } else { - Button(action: { - if #available(macOS 13, *) { - NSApp.sendAction(Selector(("showSettingsWindow:")), to: nil, from: nil) - } else { - NSApp.sendAction(Selector(("showPreferencesWindow:")), to: nil, from: nil) - } - }, label: { - Label("Preferences", systemImage: "gearshape") - }) - .help("PreferencesDescription") - } - - TextField("Search", text: $searchText) - .textFieldStyle(RoundedBorderTextFieldStyle()) - .frame(width: 200) - .help("SearchDescription") + } } } @@ -116,15 +90,13 @@ extension View { func mainToolbar( category: Binding, isInstalledOnly: Binding, - isShowingInfoPane: Binding, - searchText: Binding + isShowingInfoPane: Binding ) -> some View { self.modifier( MainToolbarModifier( category: category, isInstalledOnly: isInstalledOnly, - isShowingInfoPane: isShowingInfoPane, - searchText: searchText + isShowingInfoPane: isShowingInfoPane ) ) } diff --git a/Xcodes/Frontend/XcodeList/XcodeListView.swift b/Xcodes/Frontend/XcodeList/XcodeListView.swift index 101010f..fdbdfc8 100644 --- a/Xcodes/Frontend/XcodeList/XcodeListView.swift +++ b/Xcodes/Frontend/XcodeList/XcodeListView.swift @@ -42,6 +42,7 @@ struct XcodeListView: View { List(visibleXcodes, selection: $selectedXcodeID) { xcode in XcodeListViewRow(xcode: xcode, selected: selectedXcodeID == xcode.id, appState: appState) } + .listStyle(.sidebar) } } diff --git a/Xcodes/Resources/Localizable.xcstrings b/Xcodes/Resources/Localizable.xcstrings index c27943d..c655402 100644 --- a/Xcodes/Resources/Localizable.xcstrings +++ b/Xcodes/Resources/Localizable.xcstrings @@ -8193,6 +8193,7 @@ } }, "Info" : { + "extractionState" : "stale", "localizations" : { "ca" : { "stringUnit" : { @@ -8299,6 +8300,7 @@ } }, "InfoDescription" : { + "extractionState" : "stale", "localizations" : { "ca" : { "stringUnit" : { @@ -16125,6 +16127,7 @@ } }, "Search" : { + "extractionState" : "stale", "localizations" : { "ca" : { "stringUnit" : { @@ -16237,6 +16240,7 @@ } }, "SearchDescription" : { + "extractionState" : "stale", "localizations" : { "ca" : { "stringUnit" : {