From 7079e062d271c664efc1e346cba45d312896d8dc Mon Sep 17 00:00:00 2001 From: Duong Thai Date: Thu, 12 Oct 2023 08:49:11 +0700 Subject: [PATCH 01/17] move release date view to `ReleaseDateView.swift` - I moved outside 2 lines of `.frame(maxWidth: .infinity, alignment: .leading)` because this layout job should belong to `InfoPane` - I added preview blocks for testing --- Xcodes.xcodeproj/project.pbxproj | 4 ++ Xcodes/Frontend/InfoPane/InfoPane.swift | 19 +------ .../Frontend/InfoPane/ReleaseDateView.swift | 57 +++++++++++++++++++ 3 files changed, 63 insertions(+), 17 deletions(-) create mode 100644 Xcodes/Frontend/InfoPane/ReleaseDateView.swift diff --git a/Xcodes.xcodeproj/project.pbxproj b/Xcodes.xcodeproj/project.pbxproj index 0ea5669..d4b06bd 100644 --- a/Xcodes.xcodeproj/project.pbxproj +++ b/Xcodes.xcodeproj/project.pbxproj @@ -13,6 +13,7 @@ 536CFDD4263C9A8000026CE0 /* XcodesSheet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 536CFDD3263C9A8000026CE0 /* XcodesSheet.swift */; }; 53CBAB2C263DCC9100410495 /* XcodesAlert.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53CBAB2B263DCC9100410495 /* XcodesAlert.swift */; }; 63EAA4EB259944450046AB8F /* ProgressButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 63EAA4EA259944450046AB8F /* ProgressButton.swift */; }; + B0C6AD042AD6E65700E64698 /* ReleaseDateView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B0C6AD032AD6E65700E64698 /* ReleaseDateView.swift */; }; CA11E7BA2598476C00D2EE1C /* XcodeCommands.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA11E7B92598476C00D2EE1C /* XcodeCommands.swift */; }; CA2518EC25A7FF2B00F08414 /* AppStateUpdateTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA2518EB25A7FF2B00F08414 /* AppStateUpdateTests.swift */; }; CA25192A25A9644800F08414 /* XcodeInstallState.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA25192925A9644800F08414 /* XcodeInstallState.swift */; }; @@ -192,6 +193,7 @@ A0187D39285792D1002F46F9 /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/Localizable.strings; sourceTree = ""; }; AAB037D32839BD4700017680 /* ko */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ko; path = ko.lproj/Localizable.strings; sourceTree = ""; }; AB4EB0DE28541FA000FF3B1D /* ja */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ja; path = ja.lproj/Localizable.strings; sourceTree = ""; }; + B0C6AD032AD6E65700E64698 /* ReleaseDateView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReleaseDateView.swift; sourceTree = ""; }; B648F22B2810C1130096781B /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/Localizable.strings; sourceTree = ""; }; C0AE7FA4283002DC00DA63D2 /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/Localizable.strings"; sourceTree = ""; }; CA11E7B92598476C00D2EE1C /* XcodeCommands.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XcodeCommands.swift; sourceTree = ""; }; @@ -617,6 +619,7 @@ children = ( CAFBDC67259A308B003DCC5A /* InfoPane.swift */, E8E98A9525D863D700EC89A0 /* InstallationStepDetailView.swift */, + B0C6AD032AD6E65700E64698 /* ReleaseDateView.swift */, ); path = InfoPane; sourceTree = ""; @@ -842,6 +845,7 @@ CA9FF8662595130600E47BAF /* View+IsHidden.swift in Sources */, CAE4248C259A68B800B8B246 /* Optional+IsNotNil.swift in Sources */, CA9FF9362595B44700E47BAF /* HelperClient.swift in Sources */, + B0C6AD042AD6E65700E64698 /* ReleaseDateView.swift in Sources */, CAA8587C25A2B37900ACF8C0 /* IsTesting.swift in Sources */, CABFA9CA2592EEEA00380FEE /* AppState+Update.swift in Sources */, CA44901F2463AD34003D8213 /* Tag.swift in Sources */, diff --git a/Xcodes/Frontend/InfoPane/InfoPane.swift b/Xcodes/Frontend/InfoPane/InfoPane.swift index 3842a55..2a71240 100644 --- a/Xcodes/Frontend/InfoPane/InfoPane.swift +++ b/Xcodes/Frontend/InfoPane/InfoPane.swift @@ -55,7 +55,8 @@ struct InfoPane: View { Group{ releaseNotes(for: xcode) - releaseDate(for: xcode) + ReleaseDateView(date: xcode.releaseDate) + .frame(maxWidth: .infinity, alignment: .leading) identicalBuilds(for: xcode) compatibility(for: xcode) sdks(for: xcode) @@ -113,22 +114,6 @@ struct InfoPane: View { } } - @ViewBuilder - private func releaseDate(for xcode: Xcode) -> some View { - if let releaseDate = xcode.releaseDate { - VStack(alignment: .leading) { - Text("ReleaseDate") - .font(.headline) - .frame(maxWidth: .infinity, alignment: .leading) - Text("\(releaseDate, style: .date)") - .font(.subheadline) - .frame(maxWidth: .infinity, alignment: .leading) - } - } else { - EmptyView() - } - } - @ViewBuilder private func releaseNotes(for xcode: Xcode) -> some View { if let releaseNotesURL = xcode.releaseNotesURL { diff --git a/Xcodes/Frontend/InfoPane/ReleaseDateView.swift b/Xcodes/Frontend/InfoPane/ReleaseDateView.swift new file mode 100644 index 0000000..0695f4d --- /dev/null +++ b/Xcodes/Frontend/InfoPane/ReleaseDateView.swift @@ -0,0 +1,57 @@ +// +// ReleaseDateView.swift +// Xcodes +// +// Created by Duong Thai on 11/10/2023. +// Copyright © 2023 Robots and Pencils. All rights reserved. +// + +import SwiftUI + +struct ReleaseDateView: View { + let date: Date? + + var body: some View { + if let date = date { + VStack(alignment: .leading) { + Text("ReleaseDate (old version)") + .font(.headline) + Text("\(date, style: .date)") + .font(.subheadline) + } + } else { + EmptyView() + } + } + + init(date: Date? = nil) { + self.date = date + } +} + +struct ReleaseDateView_Preview: PreviewProvider { + static var previews: some View { + WrapperView() + } +} + +private struct WrapperView: View { + @State var isNil = false + var date: Date? { isNil ? nil : Date() } + + var body: some View { + VStack { + HStack { + ReleaseDateView(date: date) + .border(.red) + } + Spacer() + Toggle(isOn: $isNil) { + Text("Is Nil?") + } + } + .animation(.default) + .frame(width: 300, height: 100) + .padding() + } +} From e56d83dbe3767ad4c0ecf2ff43b3354453faee1f Mon Sep 17 00:00:00 2001 From: Duong Thai Date: Fri, 13 Oct 2023 13:37:41 +0700 Subject: [PATCH 02/17] move identical builds view to `IdenticalBuildView.swift` --- Xcodes.xcodeproj/project.pbxproj | 4 + .../InfoPane/IdenticalBuildView.swift | 83 +++++++++++++++++++ Xcodes/Frontend/InfoPane/InfoPane.swift | 30 +------ 3 files changed, 88 insertions(+), 29 deletions(-) create mode 100644 Xcodes/Frontend/InfoPane/IdenticalBuildView.swift diff --git a/Xcodes.xcodeproj/project.pbxproj b/Xcodes.xcodeproj/project.pbxproj index d4b06bd..cb19dcd 100644 --- a/Xcodes.xcodeproj/project.pbxproj +++ b/Xcodes.xcodeproj/project.pbxproj @@ -14,6 +14,7 @@ 53CBAB2C263DCC9100410495 /* XcodesAlert.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53CBAB2B263DCC9100410495 /* XcodesAlert.swift */; }; 63EAA4EB259944450046AB8F /* ProgressButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 63EAA4EA259944450046AB8F /* ProgressButton.swift */; }; B0C6AD042AD6E65700E64698 /* ReleaseDateView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B0C6AD032AD6E65700E64698 /* ReleaseDateView.swift */; }; + B0C6AD0B2AD9178E00E64698 /* IdenticalBuildView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B0C6AD0A2AD9178E00E64698 /* IdenticalBuildView.swift */; }; CA11E7BA2598476C00D2EE1C /* XcodeCommands.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA11E7B92598476C00D2EE1C /* XcodeCommands.swift */; }; CA2518EC25A7FF2B00F08414 /* AppStateUpdateTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA2518EB25A7FF2B00F08414 /* AppStateUpdateTests.swift */; }; CA25192A25A9644800F08414 /* XcodeInstallState.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA25192925A9644800F08414 /* XcodeInstallState.swift */; }; @@ -194,6 +195,7 @@ AAB037D32839BD4700017680 /* ko */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ko; path = ko.lproj/Localizable.strings; sourceTree = ""; }; AB4EB0DE28541FA000FF3B1D /* ja */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ja; path = ja.lproj/Localizable.strings; sourceTree = ""; }; B0C6AD032AD6E65700E64698 /* ReleaseDateView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReleaseDateView.swift; sourceTree = ""; }; + B0C6AD0A2AD9178E00E64698 /* IdenticalBuildView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IdenticalBuildView.swift; sourceTree = ""; }; B648F22B2810C1130096781B /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/Localizable.strings; sourceTree = ""; }; C0AE7FA4283002DC00DA63D2 /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/Localizable.strings"; sourceTree = ""; }; CA11E7B92598476C00D2EE1C /* XcodeCommands.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XcodeCommands.swift; sourceTree = ""; }; @@ -620,6 +622,7 @@ CAFBDC67259A308B003DCC5A /* InfoPane.swift */, E8E98A9525D863D700EC89A0 /* InstallationStepDetailView.swift */, B0C6AD032AD6E65700E64698 /* ReleaseDateView.swift */, + B0C6AD0A2AD9178E00E64698 /* IdenticalBuildView.swift */, ); path = InfoPane; sourceTree = ""; @@ -891,6 +894,7 @@ E87DD6EB25D053FA00D86808 /* Progress+.swift in Sources */, CAC281CD259F97FA00B8AB0B /* ObservingProgressIndicator.swift in Sources */, CABFA9C22592EEEA00380FEE /* Publisher+Resumable.swift in Sources */, + B0C6AD0B2AD9178E00E64698 /* IdenticalBuildView.swift in Sources */, CAFBDC68259A308B003DCC5A /* InfoPane.swift in Sources */, E87AB3C52939B65E00D72F43 /* Hardware.swift in Sources */, CAA1CB4D255A5CFD003FD669 /* SignInPhoneListView.swift in Sources */, diff --git a/Xcodes/Frontend/InfoPane/IdenticalBuildView.swift b/Xcodes/Frontend/InfoPane/IdenticalBuildView.swift new file mode 100644 index 0000000..3c78eb0 --- /dev/null +++ b/Xcodes/Frontend/InfoPane/IdenticalBuildView.swift @@ -0,0 +1,83 @@ +// +// IdenticalBuildView.swift +// Xcodes +// +// Created by Duong Thai on 11/10/2023. +// Copyright © 2023 Robots and Pencils. All rights reserved. +// + +import SwiftUI +import Version + +struct IdenticalBuildsView: View { + let builds: [Version] + private let isEmpty: Bool + private let accessibilityDescription: String + + var body: some View { + if isEmpty { + EmptyView() + } else { + VStack(alignment: .leading) { + HStack { + Text("IdenticalBuilds") + Image(systemName: "square.fill.on.square.fill") + .foregroundColor(.secondary) + .accessibility(hidden: true) + .help("IdenticalBuilds.help") + } + .font(.headline) + + ForEach(builds, id: \.description) { version in + Text("• \(version.appleDescription)") + .font(.subheadline) + } + } + .frame(maxWidth: .infinity, alignment: .leading) + .accessibilityElement() + .accessibility(label: Text("IdenticalBuilds")) + .accessibility(value: Text(accessibilityDescription)) + .accessibility(hint: Text("IdenticalBuilds.help")) + } + } + + init(builds: [Version]) { + self.builds = builds + self.isEmpty = builds.isEmpty + self.accessibilityDescription = builds + .map(\.appleDescription) + .joined(separator: ", ") + } +} + +struct IdenticalBuildsView_Preview: PreviewProvider { + static var previews: some View { + WrapperView() + } +} + +private struct WrapperView: View { + @State var isEmpty = false + var builds: [Version] { + isEmpty + ? [] + : [.init(xcodeVersion: "15.0")!, + .init(xcodeVersion: "15.1")!] + } + + var body: some View { + VStack { + HStack { + IdenticalBuildsView(builds: builds) + .border(.red) + } + Spacer() + Toggle(isOn: $isEmpty) { + Text("Is Empty?") + } + } + .animation(.default) + .frame(width: 300, height: 100) + .padding() + } +} diff --git a/Xcodes/Frontend/InfoPane/InfoPane.swift b/Xcodes/Frontend/InfoPane/InfoPane.swift index 2a71240..1af1f9c 100644 --- a/Xcodes/Frontend/InfoPane/InfoPane.swift +++ b/Xcodes/Frontend/InfoPane/InfoPane.swift @@ -57,7 +57,7 @@ struct InfoPane: View { releaseNotes(for: xcode) ReleaseDateView(date: xcode.releaseDate) .frame(maxWidth: .infinity, alignment: .leading) - identicalBuilds(for: xcode) + IdenticalBuildsView(builds: xcode.identicalBuilds) compatibility(for: xcode) sdks(for: xcode) compilers(for: xcode) @@ -86,34 +86,6 @@ struct InfoPane: View { } } - @ViewBuilder - private func identicalBuilds(for xcode: Xcode) -> some View { - if !xcode.identicalBuilds.isEmpty { - VStack(alignment: .leading) { - HStack { - Text("IdenticalBuilds") - Image(systemName: "square.fill.on.square.fill") - .foregroundColor(.secondary) - .accessibility(hidden: true) - .help("IdenticalBuilds.help") - } - .font(.headline) - - ForEach(xcode.identicalBuilds, id: \.description) { version in - Text("• \(version.appleDescription)") - .font(.subheadline) - } - } - .frame(maxWidth: .infinity, alignment: .leading) - .accessibilityElement() - .accessibility(label: Text("IdenticalBuilds")) - .accessibility(value: Text(xcode.identicalBuilds.map(\.appleDescription).joined(separator: ", "))) - .accessibility(hint: Text("IdenticalBuilds.help")) - } else { - EmptyView() - } - } - @ViewBuilder private func releaseNotes(for xcode: Xcode) -> some View { if let releaseNotesURL = xcode.releaseNotesURL { From 03eb166c3d085de2a4fb414887de024dc20fa89e Mon Sep 17 00:00:00 2001 From: Duong Thai Date: Fri, 13 Oct 2023 14:43:59 +0700 Subject: [PATCH 03/17] move icon to `IconView.swift` --- Xcodes.xcodeproj/project.pbxproj | 4 ++ Xcodes/Frontend/InfoPane/IconView.swift | 64 +++++++++++++++++++++++++ Xcodes/Frontend/InfoPane/InfoPane.swift | 14 +----- 3 files changed, 69 insertions(+), 13 deletions(-) create mode 100644 Xcodes/Frontend/InfoPane/IconView.swift diff --git a/Xcodes.xcodeproj/project.pbxproj b/Xcodes.xcodeproj/project.pbxproj index cb19dcd..e45b976 100644 --- a/Xcodes.xcodeproj/project.pbxproj +++ b/Xcodes.xcodeproj/project.pbxproj @@ -15,6 +15,7 @@ 63EAA4EB259944450046AB8F /* ProgressButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 63EAA4EA259944450046AB8F /* ProgressButton.swift */; }; B0C6AD042AD6E65700E64698 /* ReleaseDateView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B0C6AD032AD6E65700E64698 /* ReleaseDateView.swift */; }; B0C6AD0B2AD9178E00E64698 /* IdenticalBuildView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B0C6AD0A2AD9178E00E64698 /* IdenticalBuildView.swift */; }; + B0C6AD0D2AD91D7900E64698 /* IconView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B0C6AD0C2AD91D7900E64698 /* IconView.swift */; }; CA11E7BA2598476C00D2EE1C /* XcodeCommands.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA11E7B92598476C00D2EE1C /* XcodeCommands.swift */; }; CA2518EC25A7FF2B00F08414 /* AppStateUpdateTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA2518EB25A7FF2B00F08414 /* AppStateUpdateTests.swift */; }; CA25192A25A9644800F08414 /* XcodeInstallState.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA25192925A9644800F08414 /* XcodeInstallState.swift */; }; @@ -196,6 +197,7 @@ AB4EB0DE28541FA000FF3B1D /* ja */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ja; path = ja.lproj/Localizable.strings; sourceTree = ""; }; B0C6AD032AD6E65700E64698 /* ReleaseDateView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReleaseDateView.swift; sourceTree = ""; }; B0C6AD0A2AD9178E00E64698 /* IdenticalBuildView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IdenticalBuildView.swift; sourceTree = ""; }; + B0C6AD0C2AD91D7900E64698 /* IconView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IconView.swift; sourceTree = ""; }; B648F22B2810C1130096781B /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/Localizable.strings; sourceTree = ""; }; C0AE7FA4283002DC00DA63D2 /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/Localizable.strings"; sourceTree = ""; }; CA11E7B92598476C00D2EE1C /* XcodeCommands.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XcodeCommands.swift; sourceTree = ""; }; @@ -623,6 +625,7 @@ E8E98A9525D863D700EC89A0 /* InstallationStepDetailView.swift */, B0C6AD032AD6E65700E64698 /* ReleaseDateView.swift */, B0C6AD0A2AD9178E00E64698 /* IdenticalBuildView.swift */, + B0C6AD0C2AD91D7900E64698 /* IconView.swift */, ); path = InfoPane; sourceTree = ""; @@ -847,6 +850,7 @@ CABFAA492593162500380FEE /* Bundle+InfoPlistValues.swift in Sources */, CA9FF8662595130600E47BAF /* View+IsHidden.swift in Sources */, CAE4248C259A68B800B8B246 /* Optional+IsNotNil.swift in Sources */, + B0C6AD0D2AD91D7900E64698 /* IconView.swift in Sources */, CA9FF9362595B44700E47BAF /* HelperClient.swift in Sources */, B0C6AD042AD6E65700E64698 /* ReleaseDateView.swift in Sources */, CAA8587C25A2B37900ACF8C0 /* IsTesting.swift in Sources */, diff --git a/Xcodes/Frontend/InfoPane/IconView.swift b/Xcodes/Frontend/InfoPane/IconView.swift new file mode 100644 index 0000000..e7328d2 --- /dev/null +++ b/Xcodes/Frontend/InfoPane/IconView.swift @@ -0,0 +1,64 @@ +// +// IconView.swift +// Xcodes +// +// Created by Duong Thai on 11/10/2023. +// Copyright © 2023 Robots and Pencils. All rights reserved. +// + +import SwiftUI +import Path + +struct IconView: View { + let installState: XcodeInstallState + + var body: some View { + if case let .installed(path) = installState { + Image(nsImage: NSWorkspace.shared.icon(forFile: path.string)) + } else { + Image(systemName: "app.fill") + .resizable() + .frame(width: 32, height: 32) + .foregroundColor(.secondary) + } + } +} + +//#Preview { +// Group { +// IconView(path: "/Applications/Xcode.app") +// IconView() +// } +// .padding() +//} + +struct IconView_Preview: PreviewProvider { + static var previews: some View { + WrapperView() + } +} + +private struct WrapperView: View { + @State var isIcon = false + var state: XcodeInstallState { + isIcon + ? XcodeInstallState.notInstalled + : XcodeInstallState.installed(Path("/Applications/Xcode.app")!) + } + + var body: some View { + VStack { + HStack { + IconView(installState: state) + .border(.red) + } + Spacer() + Toggle(isOn: $isIcon) { + Text("Is an Icon?") + } + } + .animation(.default) + .frame(width: 300, height: 100) + .padding() + } +} diff --git a/Xcodes/Frontend/InfoPane/InfoPane.swift b/Xcodes/Frontend/InfoPane/InfoPane.swift index 1af1f9c..ed91d6d 100644 --- a/Xcodes/Frontend/InfoPane/InfoPane.swift +++ b/Xcodes/Frontend/InfoPane/InfoPane.swift @@ -14,7 +14,7 @@ struct InfoPane: View { if let xcode = appState.allXcodes.first(where: { $0.id == selectedXcodeID }) { ScrollView { VStack(alignment: .leading, spacing: 16) { - icon(for: xcode) + IconView(installState: xcode.installState) .frame(maxWidth: .infinity, alignment: .center) Text(verbatim: "Xcode \(xcode.description) \(xcode.version.buildMetadataIdentifiersDisplay)") @@ -74,18 +74,6 @@ struct InfoPane: View { } } - @ViewBuilder - private func icon(for xcode: Xcode) -> some View { - if case let .installed(path) = xcode.installState { - Image(nsImage: NSWorkspace.shared.icon(forFile: path.string)) - } else { - Image(systemName: "app.fill") - .resizable() - .frame(width: 32, height: 32) - .foregroundColor(.secondary) - } - } - @ViewBuilder private func releaseNotes(for xcode: Xcode) -> some View { if let releaseNotesURL = xcode.releaseNotesURL { From f1fea6365c972e3a6d1134dc7d11a81608995dfa Mon Sep 17 00:00:00 2001 From: Duong Thai Date: Fri, 13 Oct 2023 15:10:21 +0700 Subject: [PATCH 04/17] move release notes to `ReleaseNotesView.swift` - narrow down interfaces of some functions --- Xcodes.xcodeproj/project.pbxproj | 4 ++ Xcodes/Backend/AppState.swift | 4 +- Xcodes/Backend/XcodeCommands.swift | 7 ++- Xcodes/Frontend/InfoPane/InfoPane.swift | 24 +------ .../Frontend/InfoPane/ReleaseNotesView.swift | 62 +++++++++++++++++++ 5 files changed, 73 insertions(+), 28 deletions(-) create mode 100644 Xcodes/Frontend/InfoPane/ReleaseNotesView.swift diff --git a/Xcodes.xcodeproj/project.pbxproj b/Xcodes.xcodeproj/project.pbxproj index e45b976..701b465 100644 --- a/Xcodes.xcodeproj/project.pbxproj +++ b/Xcodes.xcodeproj/project.pbxproj @@ -13,6 +13,7 @@ 536CFDD4263C9A8000026CE0 /* XcodesSheet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 536CFDD3263C9A8000026CE0 /* XcodesSheet.swift */; }; 53CBAB2C263DCC9100410495 /* XcodesAlert.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53CBAB2B263DCC9100410495 /* XcodesAlert.swift */; }; 63EAA4EB259944450046AB8F /* ProgressButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 63EAA4EA259944450046AB8F /* ProgressButton.swift */; }; + B0403CF02AD92D7B00137C09 /* ReleaseNotesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B0403CEF2AD92D7B00137C09 /* ReleaseNotesView.swift */; }; B0C6AD042AD6E65700E64698 /* ReleaseDateView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B0C6AD032AD6E65700E64698 /* ReleaseDateView.swift */; }; B0C6AD0B2AD9178E00E64698 /* IdenticalBuildView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B0C6AD0A2AD9178E00E64698 /* IdenticalBuildView.swift */; }; B0C6AD0D2AD91D7900E64698 /* IconView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B0C6AD0C2AD91D7900E64698 /* IconView.swift */; }; @@ -195,6 +196,7 @@ A0187D39285792D1002F46F9 /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/Localizable.strings; sourceTree = ""; }; AAB037D32839BD4700017680 /* ko */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ko; path = ko.lproj/Localizable.strings; sourceTree = ""; }; AB4EB0DE28541FA000FF3B1D /* ja */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ja; path = ja.lproj/Localizable.strings; sourceTree = ""; }; + B0403CEF2AD92D7B00137C09 /* ReleaseNotesView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReleaseNotesView.swift; sourceTree = ""; }; B0C6AD032AD6E65700E64698 /* ReleaseDateView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReleaseDateView.swift; sourceTree = ""; }; B0C6AD0A2AD9178E00E64698 /* IdenticalBuildView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IdenticalBuildView.swift; sourceTree = ""; }; B0C6AD0C2AD91D7900E64698 /* IconView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IconView.swift; sourceTree = ""; }; @@ -621,6 +623,7 @@ E8E98A9425D863B100EC89A0 /* InfoPane */ = { isa = PBXGroup; children = ( + B0403CEF2AD92D7B00137C09 /* ReleaseNotesView.swift */, CAFBDC67259A308B003DCC5A /* InfoPane.swift */, E8E98A9525D863D700EC89A0 /* InstallationStepDetailView.swift */, B0C6AD032AD6E65700E64698 /* ReleaseDateView.swift */, @@ -877,6 +880,7 @@ CA61A6E0259835580008926E /* Xcode.swift in Sources */, CAE4247F259A666100B8B246 /* MainWindow.swift in Sources */, CA452BB0259FD9770072DFA4 /* ProgressIndicator.swift in Sources */, + B0403CF02AD92D7B00137C09 /* ReleaseNotesView.swift in Sources */, CAFE4AB425B7D3AF0064FE51 /* AdvancedPreferencePane.swift in Sources */, CA9FF84E2595079F00E47BAF /* ScrollingTextView.swift in Sources */, CABFA9C12592EEEA00380FEE /* Version+.swift in Sources */, diff --git a/Xcodes/Backend/AppState.swift b/Xcodes/Backend/AppState.swift index 49e22de..1c92951 100644 --- a/Xcodes/Backend/AppState.swift +++ b/Xcodes/Backend/AppState.swift @@ -630,8 +630,8 @@ class AppState: ObservableObject { NSPasteboard.general.setString(installedXcodePath.string, forType: .string) } - func copyReleaseNote(xcode: Xcode) { - guard let url = xcode.releaseNotesURL else { return } + func copyReleaseNote(from url: URL?) { + guard let url = url else { return } NSPasteboard.general.declareTypes([.URL, .string], owner: nil) NSPasteboard.general.writeObjects([url as NSURL]) NSPasteboard.general.setString(url.absoluteString, forType: .string) diff --git a/Xcodes/Backend/XcodeCommands.swift b/Xcodes/Backend/XcodeCommands.swift index 76e9924..196e25d 100644 --- a/Xcodes/Backend/XcodeCommands.swift +++ b/Xcodes/Backend/XcodeCommands.swift @@ -173,8 +173,9 @@ struct CopyPathButton: View { } struct CopyReleaseNoteButton: View { + let url: URL? + @EnvironmentObject var appState: AppState - let xcode: Xcode? var body: some View { Button(action: copyReleaseNote) { @@ -184,8 +185,8 @@ struct CopyReleaseNoteButton: View { } private func copyReleaseNote() { - guard let xcode = xcode else { return } - appState.copyReleaseNote(xcode: xcode) + guard let url = url else { return } + appState.copyReleaseNote(from: url) } } diff --git a/Xcodes/Frontend/InfoPane/InfoPane.swift b/Xcodes/Frontend/InfoPane/InfoPane.swift index ed91d6d..293d804 100644 --- a/Xcodes/Frontend/InfoPane/InfoPane.swift +++ b/Xcodes/Frontend/InfoPane/InfoPane.swift @@ -54,7 +54,7 @@ struct InfoPane: View { Divider() Group{ - releaseNotes(for: xcode) + ReleaseNotesView(url: xcode.releaseNotesURL) ReleaseDateView(date: xcode.releaseDate) .frame(maxWidth: .infinity, alignment: .leading) IdenticalBuildsView(builds: xcode.identicalBuilds) @@ -74,28 +74,6 @@ struct InfoPane: View { } } - @ViewBuilder - private func releaseNotes(for xcode: Xcode) -> some View { - if let releaseNotesURL = xcode.releaseNotesURL { - Button(action: { openURL(releaseNotesURL) }) { - Label("ReleaseNotes", systemImage: "link") - } - .buttonStyle(LinkButtonStyle()) - .contextMenu(menuItems: { - releaseNotesMenu(for: xcode) - }) - .frame(maxWidth: .infinity, alignment: .leading) - .help("ReleaseNotes.help") - } else { - EmptyView() - } - } - - @ViewBuilder - private func releaseNotesMenu(for xcode: Xcode) -> some View { - CopyReleaseNoteButton(xcode: xcode) - } - @ViewBuilder private func compatibility(for xcode: Xcode) -> some View { if let requiredMacOSVersion = xcode.requiredMacOSVersion { diff --git a/Xcodes/Frontend/InfoPane/ReleaseNotesView.swift b/Xcodes/Frontend/InfoPane/ReleaseNotesView.swift new file mode 100644 index 0000000..a9aa3c1 --- /dev/null +++ b/Xcodes/Frontend/InfoPane/ReleaseNotesView.swift @@ -0,0 +1,62 @@ +// +// ReleaseNotesView.swift +// Xcodes +// +// Created by Duong Thai on 13/10/2023. +// Copyright © 2023 Robots and Pencils. All rights reserved. +// + +import SwiftUI + +struct ReleaseNotesView: View { + let url: URL? + + @SwiftUI.Environment(\.openURL) var openURL: OpenURLAction + + var body: some View { + if let url = url { + Button(action: { openURL(url) }) { + Label("ReleaseNotes", systemImage: "link") + } + .buttonStyle(LinkButtonStyle()) + .contextMenu(menuItems: { + CopyReleaseNoteButton(url: url) + }) + .frame(maxWidth: .infinity, alignment: .leading) + .help("ReleaseNotes.help") + } else { + EmptyView() + } + } +} + +struct ReleaseNotesView_Preview: PreviewProvider { + static var previews: some View { + WrapperView() + } +} + +private struct WrapperView: View { + @State var hasURL = false + var url: URL? { + hasURL + ? nil + : URL(string: "https://developer.apple.com/documentation/xcode-release-notes/xcode-12_3-release-notes/")! + } + + var body: some View { + VStack { + HStack { + ReleaseNotesView(url: url) + .border(.red) + } + Spacer() + Toggle(isOn: $hasURL) { + Text("Has URL?") + } + } + .animation(.default) + .frame(width: 300, height: 100) + .padding() + } +} From 77ca8a2571259f0f33ba8ca64ce0b9be25ce8a38 Mon Sep 17 00:00:00 2001 From: Duong Thai Date: Fri, 13 Oct 2023 15:26:37 +0700 Subject: [PATCH 05/17] move the compatibility view to `CompatibilityView.swift` --- Xcodes.xcodeproj/project.pbxproj | 4 ++ .../Frontend/InfoPane/CompatibilityView.swift | 57 +++++++++++++++++++ Xcodes/Frontend/InfoPane/InfoPane.swift | 18 +----- 3 files changed, 62 insertions(+), 17 deletions(-) create mode 100644 Xcodes/Frontend/InfoPane/CompatibilityView.swift diff --git a/Xcodes.xcodeproj/project.pbxproj b/Xcodes.xcodeproj/project.pbxproj index 701b465..da34e5f 100644 --- a/Xcodes.xcodeproj/project.pbxproj +++ b/Xcodes.xcodeproj/project.pbxproj @@ -14,6 +14,7 @@ 53CBAB2C263DCC9100410495 /* XcodesAlert.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53CBAB2B263DCC9100410495 /* XcodesAlert.swift */; }; 63EAA4EB259944450046AB8F /* ProgressButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 63EAA4EA259944450046AB8F /* ProgressButton.swift */; }; B0403CF02AD92D7B00137C09 /* ReleaseNotesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B0403CEF2AD92D7B00137C09 /* ReleaseNotesView.swift */; }; + B0403CF22AD934B600137C09 /* CompatibilityView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B0403CF12AD934B600137C09 /* CompatibilityView.swift */; }; B0C6AD042AD6E65700E64698 /* ReleaseDateView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B0C6AD032AD6E65700E64698 /* ReleaseDateView.swift */; }; B0C6AD0B2AD9178E00E64698 /* IdenticalBuildView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B0C6AD0A2AD9178E00E64698 /* IdenticalBuildView.swift */; }; B0C6AD0D2AD91D7900E64698 /* IconView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B0C6AD0C2AD91D7900E64698 /* IconView.swift */; }; @@ -197,6 +198,7 @@ AAB037D32839BD4700017680 /* ko */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ko; path = ko.lproj/Localizable.strings; sourceTree = ""; }; AB4EB0DE28541FA000FF3B1D /* ja */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ja; path = ja.lproj/Localizable.strings; sourceTree = ""; }; B0403CEF2AD92D7B00137C09 /* ReleaseNotesView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReleaseNotesView.swift; sourceTree = ""; }; + B0403CF12AD934B600137C09 /* CompatibilityView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CompatibilityView.swift; sourceTree = ""; }; B0C6AD032AD6E65700E64698 /* ReleaseDateView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReleaseDateView.swift; sourceTree = ""; }; B0C6AD0A2AD9178E00E64698 /* IdenticalBuildView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IdenticalBuildView.swift; sourceTree = ""; }; B0C6AD0C2AD91D7900E64698 /* IconView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IconView.swift; sourceTree = ""; }; @@ -624,6 +626,7 @@ isa = PBXGroup; children = ( B0403CEF2AD92D7B00137C09 /* ReleaseNotesView.swift */, + B0403CF12AD934B600137C09 /* CompatibilityView.swift */, CAFBDC67259A308B003DCC5A /* InfoPane.swift */, E8E98A9525D863D700EC89A0 /* InstallationStepDetailView.swift */, B0C6AD032AD6E65700E64698 /* ReleaseDateView.swift */, @@ -875,6 +878,7 @@ CABFA9CD2592EEEA00380FEE /* Foundation.swift in Sources */, 36741BFF291E50F500A85AAE /* FileError.swift in Sources */, CA9FF8872595607900E47BAF /* InstalledXcode.swift in Sources */, + B0403CF22AD934B600137C09 /* CompatibilityView.swift in Sources */, 53CBAB2C263DCC9100410495 /* XcodesAlert.swift in Sources */, CA42DD6E25AEA8B200BC0B0C /* Logger.swift in Sources */, CA61A6E0259835580008926E /* Xcode.swift in Sources */, diff --git a/Xcodes/Frontend/InfoPane/CompatibilityView.swift b/Xcodes/Frontend/InfoPane/CompatibilityView.swift new file mode 100644 index 0000000..9e23e35 --- /dev/null +++ b/Xcodes/Frontend/InfoPane/CompatibilityView.swift @@ -0,0 +1,57 @@ +// +// CompatibilityView.swift +// Xcodes +// +// Created by Duong Thai on 13/10/2023. +// Copyright © 2023 Robots and Pencils. All rights reserved. +// + +import SwiftUI + +struct CompatibilityView: View { + let requiredMacOSVersion: String? + + var body: some View { + if let requiredMacOSVersion = requiredMacOSVersion { + VStack(alignment: .leading) { + Text("Compatibility") + .font(.headline) + Text(String(format: localizeString("MacOSRequirement"), requiredMacOSVersion)) + .font(.subheadline) + } + } else { + EmptyView() + } + } +} + +struct CompatibilityView_Preview: PreviewProvider { + static var previews: some View { + WrapperView() + } +} + +private struct WrapperView: View { + @State var isNil = false + var requiredMacOSVersion: String? { + isNil + ? nil + : "10.15.4" + } + + var body: some View { + VStack { + HStack { + CompatibilityView(requiredMacOSVersion: requiredMacOSVersion) + .border(.red) + } + Spacer() + Toggle(isOn: $isNil) { + Text("Is Nil?") + } + } + .animation(.default) + .frame(width: 200, height: 100) + .padding() + } +} diff --git a/Xcodes/Frontend/InfoPane/InfoPane.swift b/Xcodes/Frontend/InfoPane/InfoPane.swift index 293d804..26e1c47 100644 --- a/Xcodes/Frontend/InfoPane/InfoPane.swift +++ b/Xcodes/Frontend/InfoPane/InfoPane.swift @@ -58,7 +58,7 @@ struct InfoPane: View { ReleaseDateView(date: xcode.releaseDate) .frame(maxWidth: .infinity, alignment: .leading) IdenticalBuildsView(builds: xcode.identicalBuilds) - compatibility(for: xcode) + CompatibilityView(requiredMacOSVersion: xcode.requiredMacOSVersion) sdks(for: xcode) compilers(for: xcode) } @@ -74,22 +74,6 @@ struct InfoPane: View { } } - @ViewBuilder - private func compatibility(for xcode: Xcode) -> some View { - if let requiredMacOSVersion = xcode.requiredMacOSVersion { - VStack(alignment: .leading) { - Text("Compatibility") - .font(.headline) - .frame(maxWidth: .infinity, alignment: .leading) - Text(String(format: localizeString("MacOSRequirement"), requiredMacOSVersion)) - .font(.subheadline) - .frame(maxWidth: .infinity, alignment: .leading) - } - } else { - EmptyView() - } - } - @ViewBuilder private func sdks(for xcode: Xcode) -> some View { if let sdks = xcode.sdks { From cecceea62f50a448adf2449b9385847815edafff Mon Sep 17 00:00:00 2001 From: Duong Thai Date: Fri, 13 Oct 2023 15:31:03 +0700 Subject: [PATCH 06/17] remove `openURL` in `InfoPane` because `ReleaseNotesView` already has it --- Xcodes/Frontend/InfoPane/InfoPane.swift | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Xcodes/Frontend/InfoPane/InfoPane.swift b/Xcodes/Frontend/InfoPane/InfoPane.swift index 26e1c47..f57052f 100644 --- a/Xcodes/Frontend/InfoPane/InfoPane.swift +++ b/Xcodes/Frontend/InfoPane/InfoPane.swift @@ -8,8 +8,7 @@ import struct XCModel.Compilers struct InfoPane: View { @EnvironmentObject var appState: AppState let selectedXcodeID: Xcode.ID? - @SwiftUI.Environment(\.openURL) var openURL: OpenURLAction - + var body: some View { if let xcode = appState.allXcodes.first(where: { $0.id == selectedXcodeID }) { ScrollView { From 05f5c2cebfdc54bd8a4d16d79e6cc44846f9b8e2 Mon Sep 17 00:00:00 2001 From: Duong Thai Date: Fri, 13 Oct 2023 15:47:30 +0700 Subject: [PATCH 07/17] rebase main --- Xcodes.xcodeproj/project.pbxproj | 4 ++ Xcodes/Frontend/InfoPane/InfoPane.swift | 75 ++++++++--------------- Xcodes/Frontend/InfoPane/SDKsView.swift | 81 +++++++++++++++++++++++++ 3 files changed, 111 insertions(+), 49 deletions(-) create mode 100644 Xcodes/Frontend/InfoPane/SDKsView.swift diff --git a/Xcodes.xcodeproj/project.pbxproj b/Xcodes.xcodeproj/project.pbxproj index da34e5f..eeaa3d4 100644 --- a/Xcodes.xcodeproj/project.pbxproj +++ b/Xcodes.xcodeproj/project.pbxproj @@ -15,6 +15,7 @@ 63EAA4EB259944450046AB8F /* ProgressButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 63EAA4EA259944450046AB8F /* ProgressButton.swift */; }; B0403CF02AD92D7B00137C09 /* ReleaseNotesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B0403CEF2AD92D7B00137C09 /* ReleaseNotesView.swift */; }; B0403CF22AD934B600137C09 /* CompatibilityView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B0403CF12AD934B600137C09 /* CompatibilityView.swift */; }; + B0403CF42AD9381D00137C09 /* SDKsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B0403CF32AD9381D00137C09 /* SDKsView.swift */; }; B0C6AD042AD6E65700E64698 /* ReleaseDateView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B0C6AD032AD6E65700E64698 /* ReleaseDateView.swift */; }; B0C6AD0B2AD9178E00E64698 /* IdenticalBuildView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B0C6AD0A2AD9178E00E64698 /* IdenticalBuildView.swift */; }; B0C6AD0D2AD91D7900E64698 /* IconView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B0C6AD0C2AD91D7900E64698 /* IconView.swift */; }; @@ -199,6 +200,7 @@ AB4EB0DE28541FA000FF3B1D /* ja */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ja; path = ja.lproj/Localizable.strings; sourceTree = ""; }; B0403CEF2AD92D7B00137C09 /* ReleaseNotesView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReleaseNotesView.swift; sourceTree = ""; }; B0403CF12AD934B600137C09 /* CompatibilityView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CompatibilityView.swift; sourceTree = ""; }; + B0403CF32AD9381D00137C09 /* SDKsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SDKsView.swift; sourceTree = ""; }; B0C6AD032AD6E65700E64698 /* ReleaseDateView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReleaseDateView.swift; sourceTree = ""; }; B0C6AD0A2AD9178E00E64698 /* IdenticalBuildView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IdenticalBuildView.swift; sourceTree = ""; }; B0C6AD0C2AD91D7900E64698 /* IconView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IconView.swift; sourceTree = ""; }; @@ -626,6 +628,7 @@ isa = PBXGroup; children = ( B0403CEF2AD92D7B00137C09 /* ReleaseNotesView.swift */, + B0403CF32AD9381D00137C09 /* SDKsView.swift */, B0403CF12AD934B600137C09 /* CompatibilityView.swift */, CAFBDC67259A308B003DCC5A /* InfoPane.swift */, E8E98A9525D863D700EC89A0 /* InstallationStepDetailView.swift */, @@ -902,6 +905,7 @@ CABFAA2D2592FBFC00380FEE /* Configure.swift in Sources */, CA73510D257BFCEF00EA9CF8 /* NSAttributedString+.swift in Sources */, CAFBDB952598FE96003DCC5A /* FocusedValues.swift in Sources */, + B0403CF42AD9381D00137C09 /* SDKsView.swift in Sources */, CAC9F92D25BCDA4400B4965F /* HelperInstallState.swift in Sources */, E87DD6EB25D053FA00D86808 /* Progress+.swift in Sources */, CAC281CD259F97FA00B8AB0B /* ObservingProgressIndicator.swift in Sources */, diff --git a/Xcodes/Frontend/InfoPane/InfoPane.swift b/Xcodes/Frontend/InfoPane/InfoPane.swift index f57052f..f617165 100644 --- a/Xcodes/Frontend/InfoPane/InfoPane.swift +++ b/Xcodes/Frontend/InfoPane/InfoPane.swift @@ -2,8 +2,8 @@ import AppKit import Path import SwiftUI import Version -import struct XCModel.SDKs import struct XCModel.Compilers +import struct XCModel.SDKs struct InfoPane: View { @EnvironmentObject var appState: AppState @@ -18,12 +18,12 @@ struct InfoPane: View { Text(verbatim: "Xcode \(xcode.description) \(xcode.version.buildMetadataIdentifiersDisplay)") .font(.title) - + switch xcode.installState { case .notInstalled: InstallButton(xcode: xcode) downloadFileSize(for: xcode) - case .installing(let installationStep): + case let .installing(installationStep): InstallationStepDetailView(installationStep: installationStep) .fixedSize(horizontal: false, vertical: true) CancelInstallButton(xcode: xcode) @@ -36,32 +36,32 @@ struct InfoPane: View { .buttonStyle(PlainButtonStyle()) .help("RevealInFinder") } - + HStack { SelectButton(xcode: xcode) .disabled(xcode.selected) .help("Selected") - + OpenButton(xcode: xcode) .help("Open") - + Spacer() UninstallButton(xcode: xcode) } } - + Divider() - Group{ + Group { ReleaseNotesView(url: xcode.releaseNotesURL) ReleaseDateView(date: xcode.releaseDate) .frame(maxWidth: .infinity, alignment: .leading) IdenticalBuildsView(builds: xcode.identicalBuilds) CompatibilityView(requiredMacOSVersion: xcode.requiredMacOSVersion) - sdks(for: xcode) + SDKsView(sdks: xcode.sdks) compilers(for: xcode) } - + Spacer() } .padding() @@ -72,34 +72,7 @@ struct InfoPane: View { .frame(minWidth: 200, maxWidth: .infinity) } } - - @ViewBuilder - private func sdks(for xcode: Xcode) -> some View { - if let sdks = xcode.sdks { - VStack(alignment: .leading) { - Text("SDKs") - .font(.headline) - .frame(maxWidth: .infinity, alignment: .leading) - - ForEach([ - ("macOS", \SDKs.macOS), - ("iOS", \.iOS), - ("watchOS", \.watchOS), - ("tvOS", \.tvOS), - ("visionOS", \.visionOS), - ], id: \.0) { row in - if let sdk = sdks[keyPath: row.1] { - Text("\(row.0): \(sdk.compactMap { $0.number }.joined(separator: ", "))") - .font(.subheadline) - .frame(maxWidth: .infinity, alignment: .leading) - } - } - } - } else { - EmptyView() - } - } - + @ViewBuilder private func compilers(for xcode: Xcode) -> some View { if let compilers = xcode.compilers { @@ -107,7 +80,7 @@ struct InfoPane: View { Text("Compilers") .font(.headline) .frame(maxWidth: .infinity, alignment: .leading) - + ForEach([ ("Swift", \Compilers.swift), ("Clang", \.clang), @@ -126,7 +99,7 @@ struct InfoPane: View { EmptyView() } } - + @ViewBuilder private func downloadFileSize(for xcode: Xcode) -> some View { // if we've downloaded it no need to show the download size @@ -143,7 +116,7 @@ struct InfoPane: View { EmptyView() } } - + @ViewBuilder private var empty: some View { Text("NoXcodeSelected") @@ -181,8 +154,8 @@ struct InfoPane_Previews: PreviewProvider { clang: .init(number: "7.3"), swift: .init(number: "5.3.2") ), - downloadFileSize: 242342424 - ) + downloadFileSize: 242_342_424 + ), ] }) .previewDisplayName("Populated, Installed, Selected") @@ -208,7 +181,8 @@ struct InfoPane_Previews: PreviewProvider { clang: .init(number: "7.3"), swift: .init(number: "5.3.2") ), - downloadFileSize: 242342424) + downloadFileSize: 242_342_424 + ), ] }) .previewDisplayName("Populated, Installed, Unselected") @@ -234,7 +208,8 @@ struct InfoPane_Previews: PreviewProvider { clang: .init(number: "7.3"), swift: .init(number: "5.3.2") ), - downloadFileSize: 242342424) + downloadFileSize: 242_342_424 + ), ] }) .previewDisplayName("Populated, Uninstalled") @@ -248,7 +223,8 @@ struct InfoPane_Previews: PreviewProvider { selected: false, icon: nil, sdks: nil, - compilers: nil) + compilers: nil + ), ] }) .previewDisplayName("Basic, installed") @@ -258,15 +234,16 @@ struct InfoPane_Previews: PreviewProvider { $0.allXcodes = [ .init( version: Version(major: 12, minor: 3, patch: 1, buildMetadataIdentifiers: ["1234A"]), - installState: .installing(.downloading(progress: configure(Progress(totalUnitCount: 100)) { $0.completedUnitCount = 40; $0.throughput = 232323232; $0.fileCompletedCount = 2323004; $0.fileTotalCount = 1193939393 })), + installState: .installing(.downloading(progress: configure(Progress(totalUnitCount: 100)) { $0.completedUnitCount = 40; $0.throughput = 232_323_232; $0.fileCompletedCount = 2_323_004; $0.fileTotalCount = 1_193_939_393 })), selected: false, icon: nil, sdks: nil, - compilers: nil) + compilers: nil + ), ] }) .previewDisplayName("Basic, installing") - + InfoPane(selectedXcodeID: nil) .environmentObject(configure(AppState()) { $0.allXcodes = [ diff --git a/Xcodes/Frontend/InfoPane/SDKsView.swift b/Xcodes/Frontend/InfoPane/SDKsView.swift new file mode 100644 index 0000000..f2d3ed9 --- /dev/null +++ b/Xcodes/Frontend/InfoPane/SDKsView.swift @@ -0,0 +1,81 @@ +// +// SDKsView.swift +// Xcodes +// +// Created by Duong Thai on 13/10/2023. +// Copyright © 2023 Robots and Pencils. All rights reserved. +// + +import SwiftUI +import struct XCModel.SDKs + +struct SDKsView: View { + let sdks: SDKs? + + var body: some View { + if let sdks = sdks { + VStack(alignment: .leading) { + Text("SDKs").font(.headline) + Text(Self.content(from: sdks)).font(.subheadline) + } + } else { + EmptyView() + } + } + + static private func content(from sdks: SDKs) -> String { + let content: String = [ + ("macOS", sdks.macOS), + ("iOS", sdks.iOS), + ("watchOS", sdks.watchOS), + ("tvOS", sdks.tvOS) + ].compactMap { // remove nil compiler + guard $0.1 != nil, // has version array + !$0.1!.isEmpty // has at least 1 version + else { return nil } + + let numbers = $0.1!.compactMap { $0.number } // remove nil number + guard !numbers.isEmpty // has at least 1 number + else { return nil } + + // description for each type of compilers + return "\($0.0): \(numbers.joined(separator: ", "))" + }.joined(separator: "\n") + + return content + } +} + +struct SDKsView_Preview: PreviewProvider { + static var previews: some View { + WrapperView() + } +} + +private struct WrapperView: View { + @State var isNil = false + var sdks: SDKs? { + isNil + ? nil + : SDKs(macOS: .init(number: "11.1"), + iOS: .init(number: "14.3"), + watchOS: .init(number: "7.3"), + tvOS: .init(number: "14.3")) + } + + var body: some View { + VStack { + HStack { + SDKsView(sdks: sdks) + .border(.red) + } + Spacer() + Toggle(isOn: $isNil) { + Text("Is Nil?") + } + } + .animation(.default) + .frame(width: 200, height: 100) + .padding() + } +} From 112829b5534d253c30c5ca896fc9a3abe0cfaf9c Mon Sep 17 00:00:00 2001 From: Duong Thai Date: Thu, 23 Nov 2023 22:53:15 +0700 Subject: [PATCH 08/17] rebase main --- Xcodes.xcodeproj/project.pbxproj | 4 + Xcodes/Frontend/InfoPane/CompilersView.swift | 82 ++++++++++++++++++++ Xcodes/Frontend/InfoPane/InfoPane.swift | 29 +------ 3 files changed, 87 insertions(+), 28 deletions(-) create mode 100644 Xcodes/Frontend/InfoPane/CompilersView.swift diff --git a/Xcodes.xcodeproj/project.pbxproj b/Xcodes.xcodeproj/project.pbxproj index eeaa3d4..b108061 100644 --- a/Xcodes.xcodeproj/project.pbxproj +++ b/Xcodes.xcodeproj/project.pbxproj @@ -16,6 +16,7 @@ B0403CF02AD92D7B00137C09 /* ReleaseNotesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B0403CEF2AD92D7B00137C09 /* ReleaseNotesView.swift */; }; B0403CF22AD934B600137C09 /* CompatibilityView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B0403CF12AD934B600137C09 /* CompatibilityView.swift */; }; B0403CF42AD9381D00137C09 /* SDKsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B0403CF32AD9381D00137C09 /* SDKsView.swift */; }; + B0403CF62AD9849E00137C09 /* CompilersView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B0403CF52AD9849E00137C09 /* CompilersView.swift */; }; B0C6AD042AD6E65700E64698 /* ReleaseDateView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B0C6AD032AD6E65700E64698 /* ReleaseDateView.swift */; }; B0C6AD0B2AD9178E00E64698 /* IdenticalBuildView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B0C6AD0A2AD9178E00E64698 /* IdenticalBuildView.swift */; }; B0C6AD0D2AD91D7900E64698 /* IconView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B0C6AD0C2AD91D7900E64698 /* IconView.swift */; }; @@ -201,6 +202,7 @@ B0403CEF2AD92D7B00137C09 /* ReleaseNotesView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReleaseNotesView.swift; sourceTree = ""; }; B0403CF12AD934B600137C09 /* CompatibilityView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CompatibilityView.swift; sourceTree = ""; }; B0403CF32AD9381D00137C09 /* SDKsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SDKsView.swift; sourceTree = ""; }; + B0403CF52AD9849E00137C09 /* CompilersView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CompilersView.swift; sourceTree = ""; }; B0C6AD032AD6E65700E64698 /* ReleaseDateView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReleaseDateView.swift; sourceTree = ""; }; B0C6AD0A2AD9178E00E64698 /* IdenticalBuildView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IdenticalBuildView.swift; sourceTree = ""; }; B0C6AD0C2AD91D7900E64698 /* IconView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IconView.swift; sourceTree = ""; }; @@ -629,6 +631,7 @@ children = ( B0403CEF2AD92D7B00137C09 /* ReleaseNotesView.swift */, B0403CF32AD9381D00137C09 /* SDKsView.swift */, + B0403CF52AD9849E00137C09 /* CompilersView.swift */, B0403CF12AD934B600137C09 /* CompatibilityView.swift */, CAFBDC67259A308B003DCC5A /* InfoPane.swift */, E8E98A9525D863D700EC89A0 /* InstallationStepDetailView.swift */, @@ -921,6 +924,7 @@ CAE424B4259A764700B8B246 /* AppState+Install.swift in Sources */, CAE42487259A68A300B8B246 /* XcodeListCategory.swift in Sources */, CAA858C425A2BE4E00ACF8C0 /* Downloader.swift in Sources */, + B0403CF62AD9849E00137C09 /* CompilersView.swift in Sources */, E8977EA325C11E1500835F80 /* PreferencesView.swift in Sources */, CA9FF87B2595293E00E47BAF /* DataSource.swift in Sources */, CABFA9C92592EEEA00380FEE /* URLRequest+Apple.swift in Sources */, diff --git a/Xcodes/Frontend/InfoPane/CompilersView.swift b/Xcodes/Frontend/InfoPane/CompilersView.swift new file mode 100644 index 0000000..68d2f52 --- /dev/null +++ b/Xcodes/Frontend/InfoPane/CompilersView.swift @@ -0,0 +1,82 @@ +// +// CompilersView.swift +// Xcodes +// +// Created by Duong Thai on 13/10/2023. +// Copyright © 2023 Robots and Pencils. All rights reserved. +// + +import SwiftUI +import struct XCModel.Compilers + +struct CompilersView: View { + let compilers: Compilers? + + var body: some View { + if let compilers = compilers { + VStack(alignment: .leading) { + Text("Compilers").font(.headline) + Text(Self.content(from: compilers)).font(.subheadline) + } + } else { + EmptyView() + } + } + + static func content(from compilers: Compilers) -> String { + [ ("Swift", compilers.swift), + ("Clang", compilers.clang), + ("LLVM", compilers.llvm), + ("LLVM GCC", compilers.llvm_gcc), + ("GCC", compilers.gcc) + ].compactMap { // remove nil compiler + guard $0.1 != nil, // has version array + !$0.1!.isEmpty // has at least 1 version + else { return nil } + + let numbers = $0.1!.compactMap { $0.number } // remove nil number + guard !numbers.isEmpty // has at least 1 number + else { return nil } + + // description for each type of compilers + return "\($0.0): \(numbers.joined(separator: ", "))" + }.joined(separator: "\n") + } +} + +struct CompilersView_Preview: PreviewProvider { + static var previews: some View { + WrapperView() + } +} + +private struct WrapperView: View { + @State var isNil = false + var compilers: Compilers? { + isNil + ? nil + : Compilers( + gcc: .init(number: "4"), + llvm_gcc: .init(number: "213"), + llvm: .init(number: "2.3"), + clang: .init(number: "7.3"), + swift: .init(number: "5.3.2")) + } + + var body: some View { + VStack { + HStack { + CompilersView(compilers: compilers) + .border(.red) + } + Spacer() + Toggle(isOn: $isNil) { + Text("Is Nil?") + } + } + .animation(.default) + .frame(width: 200, height: 100) + .padding() + } +} + diff --git a/Xcodes/Frontend/InfoPane/InfoPane.swift b/Xcodes/Frontend/InfoPane/InfoPane.swift index f617165..5cf6eb2 100644 --- a/Xcodes/Frontend/InfoPane/InfoPane.swift +++ b/Xcodes/Frontend/InfoPane/InfoPane.swift @@ -59,7 +59,7 @@ struct InfoPane: View { IdenticalBuildsView(builds: xcode.identicalBuilds) CompatibilityView(requiredMacOSVersion: xcode.requiredMacOSVersion) SDKsView(sdks: xcode.sdks) - compilers(for: xcode) + CompilersView(compilers: xcode.compilers) } Spacer() @@ -73,33 +73,6 @@ struct InfoPane: View { } } - @ViewBuilder - private func compilers(for xcode: Xcode) -> some View { - if let compilers = xcode.compilers { - VStack(alignment: .leading) { - Text("Compilers") - .font(.headline) - .frame(maxWidth: .infinity, alignment: .leading) - - ForEach([ - ("Swift", \Compilers.swift), - ("Clang", \.clang), - ("LLVM", \.llvm), - ("LLVM GCC", \.llvm_gcc), - ("GCC", \.gcc), - ], id: \.0) { row in - if let sdk = compilers[keyPath: row.1] { - Text("\(row.0): \(sdk.compactMap { $0.number }.joined(separator: ", "))") - .font(.subheadline) - .frame(maxWidth: .infinity, alignment: .leading) - } - } - } - } else { - EmptyView() - } - } - @ViewBuilder private func downloadFileSize(for xcode: Xcode) -> some View { // if we've downloaded it no need to show the download size From d08d2d38e4ee140de70c564dea918991722236e2 Mon Sep 17 00:00:00 2001 From: Duong Thai Date: Fri, 13 Oct 2023 22:02:17 +0700 Subject: [PATCH 09/17] move and fix the `UnselectedView` rebase main --- Xcodes.xcodeproj/project.pbxproj | 4 ++++ Xcodes/Frontend/InfoPane/InfoPane.swift | 12 +--------- Xcodes/Frontend/InfoPane/UnselectedView.swift | 24 +++++++++++++++++++ 3 files changed, 29 insertions(+), 11 deletions(-) create mode 100644 Xcodes/Frontend/InfoPane/UnselectedView.swift diff --git a/Xcodes.xcodeproj/project.pbxproj b/Xcodes.xcodeproj/project.pbxproj index b108061..82ec9d9 100644 --- a/Xcodes.xcodeproj/project.pbxproj +++ b/Xcodes.xcodeproj/project.pbxproj @@ -17,6 +17,7 @@ B0403CF22AD934B600137C09 /* CompatibilityView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B0403CF12AD934B600137C09 /* CompatibilityView.swift */; }; B0403CF42AD9381D00137C09 /* SDKsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B0403CF32AD9381D00137C09 /* SDKsView.swift */; }; B0403CF62AD9849E00137C09 /* CompilersView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B0403CF52AD9849E00137C09 /* CompilersView.swift */; }; + B0403CF82AD991F800137C09 /* UnselectedView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B0403CF72AD991F800137C09 /* UnselectedView.swift */; }; B0C6AD042AD6E65700E64698 /* ReleaseDateView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B0C6AD032AD6E65700E64698 /* ReleaseDateView.swift */; }; B0C6AD0B2AD9178E00E64698 /* IdenticalBuildView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B0C6AD0A2AD9178E00E64698 /* IdenticalBuildView.swift */; }; B0C6AD0D2AD91D7900E64698 /* IconView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B0C6AD0C2AD91D7900E64698 /* IconView.swift */; }; @@ -203,6 +204,7 @@ B0403CF12AD934B600137C09 /* CompatibilityView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CompatibilityView.swift; sourceTree = ""; }; B0403CF32AD9381D00137C09 /* SDKsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SDKsView.swift; sourceTree = ""; }; B0403CF52AD9849E00137C09 /* CompilersView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CompilersView.swift; sourceTree = ""; }; + B0403CF72AD991F800137C09 /* UnselectedView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnselectedView.swift; sourceTree = ""; }; B0C6AD032AD6E65700E64698 /* ReleaseDateView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReleaseDateView.swift; sourceTree = ""; }; B0C6AD0A2AD9178E00E64698 /* IdenticalBuildView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IdenticalBuildView.swift; sourceTree = ""; }; B0C6AD0C2AD91D7900E64698 /* IconView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IconView.swift; sourceTree = ""; }; @@ -634,6 +636,7 @@ B0403CF52AD9849E00137C09 /* CompilersView.swift */, B0403CF12AD934B600137C09 /* CompatibilityView.swift */, CAFBDC67259A308B003DCC5A /* InfoPane.swift */, + B0403CF72AD991F800137C09 /* UnselectedView.swift */, E8E98A9525D863D700EC89A0 /* InstallationStepDetailView.swift */, B0C6AD032AD6E65700E64698 /* ReleaseDateView.swift */, B0C6AD0A2AD9178E00E64698 /* IdenticalBuildView.swift */, @@ -915,6 +918,7 @@ CABFA9C22592EEEA00380FEE /* Publisher+Resumable.swift in Sources */, B0C6AD0B2AD9178E00E64698 /* IdenticalBuildView.swift in Sources */, CAFBDC68259A308B003DCC5A /* InfoPane.swift in Sources */, + B0403CF82AD991F800137C09 /* UnselectedView.swift in Sources */, E87AB3C52939B65E00D72F43 /* Hardware.swift in Sources */, CAA1CB4D255A5CFD003FD669 /* SignInPhoneListView.swift in Sources */, CAFBDC6C259A3098003DCC5A /* View+Conditional.swift in Sources */, diff --git a/Xcodes/Frontend/InfoPane/InfoPane.swift b/Xcodes/Frontend/InfoPane/InfoPane.swift index 5cf6eb2..9cb9abe 100644 --- a/Xcodes/Frontend/InfoPane/InfoPane.swift +++ b/Xcodes/Frontend/InfoPane/InfoPane.swift @@ -68,8 +68,7 @@ struct InfoPane: View { } .frame(minWidth: 200, maxWidth: .infinity) } else { - empty - .frame(minWidth: 200, maxWidth: .infinity) + UnselectedView() } } @@ -89,15 +88,6 @@ struct InfoPane: View { EmptyView() } } - - @ViewBuilder - private var empty: some View { - Text("NoXcodeSelected") - .font(.title) - .foregroundColor(.secondary) - .frame(maxWidth: .infinity, maxHeight: .infinity) - .padding() - } } struct InfoPane_Previews: PreviewProvider { diff --git a/Xcodes/Frontend/InfoPane/UnselectedView.swift b/Xcodes/Frontend/InfoPane/UnselectedView.swift new file mode 100644 index 0000000..4b6c2af --- /dev/null +++ b/Xcodes/Frontend/InfoPane/UnselectedView.swift @@ -0,0 +1,24 @@ +// +// UnselectedView.swift +// Xcodes +// +// Created by Duong Thai on 13/10/2023. +// Copyright © 2023 Robots and Pencils. All rights reserved. +// + +import SwiftUI + +struct UnselectedView: View { + var body: some View { + Text("NoXcodeSelected") + .font(.title) + .foregroundColor(.secondary) + } +} + +struct UnselectedView_Preview: PreviewProvider { + static var previews: some View { + UnselectedView() + .padding() + } +} From d172df68e3cb8b662d2848ac73befa42458abdde Mon Sep 17 00:00:00 2001 From: Duong Thai Date: Fri, 13 Oct 2023 23:18:55 +0700 Subject: [PATCH 10/17] move and fix the `NotInstalledStateButtonsView` --- Xcodes.xcodeproj/project.pbxproj | 4 ++ Xcodes/Frontend/InfoPane/InfoPane.swift | 6 +- .../NotInstalledStateButtonsView.swift | 68 +++++++++++++++++++ 3 files changed, 76 insertions(+), 2 deletions(-) create mode 100644 Xcodes/Frontend/InfoPane/NotInstalledStateButtonsView.swift diff --git a/Xcodes.xcodeproj/project.pbxproj b/Xcodes.xcodeproj/project.pbxproj index 82ec9d9..1198295 100644 --- a/Xcodes.xcodeproj/project.pbxproj +++ b/Xcodes.xcodeproj/project.pbxproj @@ -18,6 +18,7 @@ B0403CF42AD9381D00137C09 /* SDKsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B0403CF32AD9381D00137C09 /* SDKsView.swift */; }; B0403CF62AD9849E00137C09 /* CompilersView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B0403CF52AD9849E00137C09 /* CompilersView.swift */; }; B0403CF82AD991F800137C09 /* UnselectedView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B0403CF72AD991F800137C09 /* UnselectedView.swift */; }; + B0403CFA2AD9942A00137C09 /* NotInstalledStateButtonsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B0403CF92AD9942A00137C09 /* NotInstalledStateButtonsView.swift */; }; B0C6AD042AD6E65700E64698 /* ReleaseDateView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B0C6AD032AD6E65700E64698 /* ReleaseDateView.swift */; }; B0C6AD0B2AD9178E00E64698 /* IdenticalBuildView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B0C6AD0A2AD9178E00E64698 /* IdenticalBuildView.swift */; }; B0C6AD0D2AD91D7900E64698 /* IconView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B0C6AD0C2AD91D7900E64698 /* IconView.swift */; }; @@ -205,6 +206,7 @@ B0403CF32AD9381D00137C09 /* SDKsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SDKsView.swift; sourceTree = ""; }; B0403CF52AD9849E00137C09 /* CompilersView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CompilersView.swift; sourceTree = ""; }; B0403CF72AD991F800137C09 /* UnselectedView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnselectedView.swift; sourceTree = ""; }; + B0403CF92AD9942A00137C09 /* NotInstalledStateButtonsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotInstalledStateButtonsView.swift; sourceTree = ""; }; B0C6AD032AD6E65700E64698 /* ReleaseDateView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReleaseDateView.swift; sourceTree = ""; }; B0C6AD0A2AD9178E00E64698 /* IdenticalBuildView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IdenticalBuildView.swift; sourceTree = ""; }; B0C6AD0C2AD91D7900E64698 /* IconView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IconView.swift; sourceTree = ""; }; @@ -637,6 +639,7 @@ B0403CF12AD934B600137C09 /* CompatibilityView.swift */, CAFBDC67259A308B003DCC5A /* InfoPane.swift */, B0403CF72AD991F800137C09 /* UnselectedView.swift */, + B0403CF92AD9942A00137C09 /* NotInstalledStateButtonsView.swift */, E8E98A9525D863D700EC89A0 /* InstallationStepDetailView.swift */, B0C6AD032AD6E65700E64698 /* ReleaseDateView.swift */, B0C6AD0A2AD9178E00E64698 /* IdenticalBuildView.swift */, @@ -925,6 +928,7 @@ CABFA9CF2592EEEA00380FEE /* Process.swift in Sources */, CAFFFED8259CDA5000903F81 /* XcodeListViewRow.swift in Sources */, CABFA9C72592EEEA00380FEE /* Entry+.swift in Sources */, + B0403CFA2AD9942A00137C09 /* NotInstalledStateButtonsView.swift in Sources */, CAE424B4259A764700B8B246 /* AppState+Install.swift in Sources */, CAE42487259A68A300B8B246 /* XcodeListCategory.swift in Sources */, CAA858C425A2BE4E00ACF8C0 /* Downloader.swift in Sources */, diff --git a/Xcodes/Frontend/InfoPane/InfoPane.swift b/Xcodes/Frontend/InfoPane/InfoPane.swift index 9cb9abe..5125ab1 100644 --- a/Xcodes/Frontend/InfoPane/InfoPane.swift +++ b/Xcodes/Frontend/InfoPane/InfoPane.swift @@ -21,8 +21,10 @@ struct InfoPane: View { switch xcode.installState { case .notInstalled: - InstallButton(xcode: xcode) - downloadFileSize(for: xcode) + NotInstalledStateButtonsView( + downloadFileSizeString: xcode.downloadFileSizeString, + id: xcode.id + ) case let .installing(installationStep): InstallationStepDetailView(installationStep: installationStep) .fixedSize(horizontal: false, vertical: true) diff --git a/Xcodes/Frontend/InfoPane/NotInstalledStateButtonsView.swift b/Xcodes/Frontend/InfoPane/NotInstalledStateButtonsView.swift new file mode 100644 index 0000000..4935362 --- /dev/null +++ b/Xcodes/Frontend/InfoPane/NotInstalledStateButtonsView.swift @@ -0,0 +1,68 @@ +// +// NotInstalledStateButtonsView.swift +// Xcodes +// +// Created by Duong Thai on 13/10/2023. +// Copyright © 2023 Robots and Pencils. All rights reserved. +// + +import SwiftUI +import Version + +struct NotInstalledStateButtonsView: View { + let downloadFileSizeString: String? + let id: Version + + @EnvironmentObject var appState: AppState + + var body: some View { + VStack(alignment: .leading) { + Button { + appState.checkMinVersionAndInstall(id: id) + } label: { + Text("Install") .help("Install") + } + if let size = downloadFileSizeString { + + Text("DownloadSize") + .font(.headline) + Text(size) + .font(.subheadline) + } else { + EmptyView() + } + } + } +} + +struct NotInstalledStateButtonsView_Preview: PreviewProvider { + static var previews: some View { + WrapperView() + } +} + +private struct WrapperView: View { + @State var isSizeNil = false + + var downloadFileSizeString: String? { + isSizeNil + ? nil + : "1,19 GB" + } + + var body: some View { + VStack { + NotInstalledStateButtonsView( + downloadFileSizeString: downloadFileSizeString, + id: Version(major: 12, minor: 3, patch: 0) + ) + .border(.red) + Spacer() + Toggle(isOn: $isSizeNil) { + Text("Is Size Nil?") + } + } + .frame(width: 200, height: 100) + .padding() + } +} From 9e787f98d898b1dbf0ba6f054c7a78e3ad9f988e Mon Sep 17 00:00:00 2001 From: Duong Thai Date: Fri, 13 Oct 2023 23:22:26 +0700 Subject: [PATCH 11/17] fix layout of `UnselectedView` --- Xcodes/Frontend/InfoPane/UnselectedView.swift | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/Xcodes/Frontend/InfoPane/UnselectedView.swift b/Xcodes/Frontend/InfoPane/UnselectedView.swift index 4b6c2af..f0d5ea4 100644 --- a/Xcodes/Frontend/InfoPane/UnselectedView.swift +++ b/Xcodes/Frontend/InfoPane/UnselectedView.swift @@ -10,9 +10,13 @@ import SwiftUI struct UnselectedView: View { var body: some View { - Text("NoXcodeSelected") - .font(.title) - .foregroundColor(.secondary) + VStack { + Spacer() + Text("NoXcodeSelected") + .font(.title) + .foregroundColor(.secondary) + Spacer() + } } } From d406972e81384e8f224d812407ff61d21c3c31de Mon Sep 17 00:00:00 2001 From: Duong Thai Date: Sat, 14 Oct 2023 00:31:20 +0700 Subject: [PATCH 12/17] move and fix the `InstalledStateButtons` --- Xcodes.xcodeproj/project.pbxproj | 12 ++- Xcodes/Backend/AppState.swift | 6 +- Xcodes/Backend/XcodeCommands.swift | 2 +- Xcodes/Frontend/InfoPane/InfoPane.swift | 25 +----- .../InfoPane/InstalledStateButtons.swift | 80 +++++++++++++++++++ ...w.swift => NotInstalledStateButtons.swift} | 6 +- 6 files changed, 98 insertions(+), 33 deletions(-) create mode 100644 Xcodes/Frontend/InfoPane/InstalledStateButtons.swift rename Xcodes/Frontend/InfoPane/{NotInstalledStateButtonsView.swift => NotInstalledStateButtons.swift} (90%) diff --git a/Xcodes.xcodeproj/project.pbxproj b/Xcodes.xcodeproj/project.pbxproj index 1198295..f0e0f6d 100644 --- a/Xcodes.xcodeproj/project.pbxproj +++ b/Xcodes.xcodeproj/project.pbxproj @@ -18,7 +18,8 @@ B0403CF42AD9381D00137C09 /* SDKsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B0403CF32AD9381D00137C09 /* SDKsView.swift */; }; B0403CF62AD9849E00137C09 /* CompilersView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B0403CF52AD9849E00137C09 /* CompilersView.swift */; }; B0403CF82AD991F800137C09 /* UnselectedView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B0403CF72AD991F800137C09 /* UnselectedView.swift */; }; - B0403CFA2AD9942A00137C09 /* NotInstalledStateButtonsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B0403CF92AD9942A00137C09 /* NotInstalledStateButtonsView.swift */; }; + B0403CFA2AD9942A00137C09 /* NotInstalledStateButtons.swift in Sources */ = {isa = PBXBuildFile; fileRef = B0403CF92AD9942A00137C09 /* NotInstalledStateButtons.swift */; }; + B0403CFC2AD9A6BF00137C09 /* InstalledStateButtons.swift in Sources */ = {isa = PBXBuildFile; fileRef = B0403CFB2AD9A6BF00137C09 /* InstalledStateButtons.swift */; }; B0C6AD042AD6E65700E64698 /* ReleaseDateView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B0C6AD032AD6E65700E64698 /* ReleaseDateView.swift */; }; B0C6AD0B2AD9178E00E64698 /* IdenticalBuildView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B0C6AD0A2AD9178E00E64698 /* IdenticalBuildView.swift */; }; B0C6AD0D2AD91D7900E64698 /* IconView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B0C6AD0C2AD91D7900E64698 /* IconView.swift */; }; @@ -206,7 +207,8 @@ B0403CF32AD9381D00137C09 /* SDKsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SDKsView.swift; sourceTree = ""; }; B0403CF52AD9849E00137C09 /* CompilersView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CompilersView.swift; sourceTree = ""; }; B0403CF72AD991F800137C09 /* UnselectedView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnselectedView.swift; sourceTree = ""; }; - B0403CF92AD9942A00137C09 /* NotInstalledStateButtonsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotInstalledStateButtonsView.swift; sourceTree = ""; }; + B0403CF92AD9942A00137C09 /* NotInstalledStateButtons.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotInstalledStateButtons.swift; sourceTree = ""; }; + B0403CFB2AD9A6BF00137C09 /* InstalledStateButtons.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstalledStateButtons.swift; sourceTree = ""; }; B0C6AD032AD6E65700E64698 /* ReleaseDateView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReleaseDateView.swift; sourceTree = ""; }; B0C6AD0A2AD9178E00E64698 /* IdenticalBuildView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IdenticalBuildView.swift; sourceTree = ""; }; B0C6AD0C2AD91D7900E64698 /* IconView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IconView.swift; sourceTree = ""; }; @@ -639,7 +641,8 @@ B0403CF12AD934B600137C09 /* CompatibilityView.swift */, CAFBDC67259A308B003DCC5A /* InfoPane.swift */, B0403CF72AD991F800137C09 /* UnselectedView.swift */, - B0403CF92AD9942A00137C09 /* NotInstalledStateButtonsView.swift */, + B0403CF92AD9942A00137C09 /* NotInstalledStateButtons.swift */, + B0403CFB2AD9A6BF00137C09 /* InstalledStateButtons.swift */, E8E98A9525D863D700EC89A0 /* InstallationStepDetailView.swift */, B0C6AD032AD6E65700E64698 /* ReleaseDateView.swift */, B0C6AD0A2AD9178E00E64698 /* IdenticalBuildView.swift */, @@ -888,6 +891,7 @@ CAA1CB45255A5B60003FD669 /* SignIn2FAView.swift in Sources */, CABFA9C52592EEEA00380FEE /* FileManager+.swift in Sources */, CABFA9CD2592EEEA00380FEE /* Foundation.swift in Sources */, + B0403CFC2AD9A6BF00137C09 /* InstalledStateButtons.swift in Sources */, 36741BFF291E50F500A85AAE /* FileError.swift in Sources */, CA9FF8872595607900E47BAF /* InstalledXcode.swift in Sources */, B0403CF22AD934B600137C09 /* CompatibilityView.swift in Sources */, @@ -928,7 +932,7 @@ CABFA9CF2592EEEA00380FEE /* Process.swift in Sources */, CAFFFED8259CDA5000903F81 /* XcodeListViewRow.swift in Sources */, CABFA9C72592EEEA00380FEE /* Entry+.swift in Sources */, - B0403CFA2AD9942A00137C09 /* NotInstalledStateButtonsView.swift in Sources */, + B0403CFA2AD9942A00137C09 /* NotInstalledStateButtons.swift in Sources */, CAE424B4259A764700B8B246 /* AppState+Install.swift in Sources */, CAE42487259A68A300B8B246 /* XcodeListCategory.swift in Sources */, CAA858C425A2BE4E00ACF8C0 /* Downloader.swift in Sources */, diff --git a/Xcodes/Backend/AppState.swift b/Xcodes/Backend/AppState.swift index 1c92951..168369f 100644 --- a/Xcodes/Backend/AppState.swift +++ b/Xcodes/Backend/AppState.swift @@ -541,10 +541,10 @@ class AppState: ObservableObject { ) } - func reveal(xcode: Xcode) { + func reveal(_ path: Path?) { // TODO: show error if not - guard let installedXcodePath = xcode.installedPath else { return } - NSWorkspace.shared.activateFileViewerSelecting([installedXcodePath.url]) + guard let path = path else { return } + NSWorkspace.shared.activateFileViewerSelecting([path.url]) } func reveal(path: String) { diff --git a/Xcodes/Backend/XcodeCommands.swift b/Xcodes/Backend/XcodeCommands.swift index 196e25d..6767afe 100644 --- a/Xcodes/Backend/XcodeCommands.swift +++ b/Xcodes/Backend/XcodeCommands.swift @@ -151,7 +151,7 @@ struct RevealButton: View { private func reveal() { guard let xcode = xcode else { return } - appState.reveal(xcode: xcode) + appState.reveal(xcode.installedPath) } } diff --git a/Xcodes/Frontend/InfoPane/InfoPane.swift b/Xcodes/Frontend/InfoPane/InfoPane.swift index 5125ab1..e242bcd 100644 --- a/Xcodes/Frontend/InfoPane/InfoPane.swift +++ b/Xcodes/Frontend/InfoPane/InfoPane.swift @@ -21,7 +21,7 @@ struct InfoPane: View { switch xcode.installState { case .notInstalled: - NotInstalledStateButtonsView( + NotInstalledStateButtons( downloadFileSizeString: xcode.downloadFileSizeString, id: xcode.id ) @@ -29,27 +29,8 @@ struct InfoPane: View { InstallationStepDetailView(installationStep: installationStep) .fixedSize(horizontal: false, vertical: true) CancelInstallButton(xcode: xcode) - case let .installed(path): - HStack { - Text(path.string) - Button(action: { appState.reveal(xcode: xcode) }) { - Image(systemName: "arrow.right.circle.fill") - } - .buttonStyle(PlainButtonStyle()) - .help("RevealInFinder") - } - - HStack { - SelectButton(xcode: xcode) - .disabled(xcode.selected) - .help("Selected") - - OpenButton(xcode: xcode) - .help("Open") - - Spacer() - UninstallButton(xcode: xcode) - } + case .installed: + InstalledStateButtons(xcode: xcode) } Divider() diff --git a/Xcodes/Frontend/InfoPane/InstalledStateButtons.swift b/Xcodes/Frontend/InfoPane/InstalledStateButtons.swift new file mode 100644 index 0000000..de1c73c --- /dev/null +++ b/Xcodes/Frontend/InfoPane/InstalledStateButtons.swift @@ -0,0 +1,80 @@ +// +// InstallingStateButtons.swift +// Xcodes +// +// Created by Duong Thai on 13/10/2023. +// Copyright © 2023 Robots and Pencils. All rights reserved. +// + +import SwiftUI +import Version +import XCModel +import Path + +struct InstalledStateButtons: View { + let xcode: Xcode + + @EnvironmentObject var appState: AppState + + var body: some View { + VStack(alignment: .leading) { + HStack { + Text(xcode.installedPath?.string ?? "") + Button(action: { appState.reveal(xcode.installedPath) }) { + Image(systemName: "arrow.right.circle.fill") + } + .buttonStyle(PlainButtonStyle()) + .help("RevealInFinder") + } + + HStack { + SelectButton(xcode: xcode) + .disabled(xcode.selected) + .help("Selected") + + OpenButton(xcode: xcode) + .help("Open") + + Spacer() + UninstallButton(xcode: xcode) + } + + } + } +} + +struct InstalledStateButtons_Preview: PreviewProvider { + static var previews: some View { + InstalledStateButtons(xcode: Self.xcode) + .environmentObject(configure(AppState()) { + $0.allXcodes = [Self.xcode] + }) + .padding() + .frame(width: 300) + } + + static private let xcode = Xcode( + version: Version(major: 12, minor: 3, patch: 0), + installState: .installed(Path("/Applications/Xcode-12.3.0.app")!), + selected: true, + icon: NSWorkspace.shared.icon(forFile: "/Applications/Xcode-12.3.0.app"), + requiredMacOSVersion: "10.15.4", + releaseNotesURL: URL(string: "https://developer.apple.com/documentation/xcode-release-notes/xcode-12_3-release-notes/")!, + releaseDate: Date(), + sdks: SDKs( + macOS: .init(number: "11.1"), + iOS: .init(number: "14.3"), + watchOS: .init(number: "7.3"), + tvOS: .init(number: "14.3") + ), + compilers: Compilers( + gcc: .init(number: "4"), + llvm_gcc: .init(number: "213"), + llvm: .init(number: "2.3"), + clang: .init(number: "7.3"), + swift: .init(number: "5.3.2") + ), + downloadFileSize: 242342424 + ) +} + diff --git a/Xcodes/Frontend/InfoPane/NotInstalledStateButtonsView.swift b/Xcodes/Frontend/InfoPane/NotInstalledStateButtons.swift similarity index 90% rename from Xcodes/Frontend/InfoPane/NotInstalledStateButtonsView.swift rename to Xcodes/Frontend/InfoPane/NotInstalledStateButtons.swift index 4935362..db81d9d 100644 --- a/Xcodes/Frontend/InfoPane/NotInstalledStateButtonsView.swift +++ b/Xcodes/Frontend/InfoPane/NotInstalledStateButtons.swift @@ -9,7 +9,7 @@ import SwiftUI import Version -struct NotInstalledStateButtonsView: View { +struct NotInstalledStateButtons: View { let downloadFileSizeString: String? let id: Version @@ -35,7 +35,7 @@ struct NotInstalledStateButtonsView: View { } } -struct NotInstalledStateButtonsView_Preview: PreviewProvider { +struct NotInstalledStateButtons_Preview: PreviewProvider { static var previews: some View { WrapperView() } @@ -52,7 +52,7 @@ private struct WrapperView: View { var body: some View { VStack { - NotInstalledStateButtonsView( + NotInstalledStateButtons( downloadFileSizeString: downloadFileSizeString, id: Version(major: 12, minor: 3, patch: 0) ) From 4e55aac679e0c9416d94af291e7621d686895438 Mon Sep 17 00:00:00 2001 From: Duong Thai Date: Sat, 14 Oct 2023 00:49:55 +0700 Subject: [PATCH 13/17] clean up code --- .../Frontend/InfoPane/CompatibilityView.swift | 1 - Xcodes/Frontend/InfoPane/CompilersView.swift | 7 ++----- Xcodes/Frontend/InfoPane/IconView.swift | 13 +++++-------- .../InfoPane/IdenticalBuildView.swift | 8 ++------ Xcodes/Frontend/InfoPane/InfoPane.swift | 19 ------------------- .../InfoPane/InstallationStepDetailView.swift | 3 ++- .../InfoPane/InstalledStateButtons.swift | 1 - .../InfoPane/NotInstalledStateButtons.swift | 2 +- .../Frontend/InfoPane/ReleaseDateView.swift | 9 +++------ .../Frontend/InfoPane/ReleaseNotesView.swift | 6 +----- Xcodes/Frontend/InfoPane/SDKsView.swift | 6 +----- 11 files changed, 17 insertions(+), 58 deletions(-) diff --git a/Xcodes/Frontend/InfoPane/CompatibilityView.swift b/Xcodes/Frontend/InfoPane/CompatibilityView.swift index 9e23e35..9b60998 100644 --- a/Xcodes/Frontend/InfoPane/CompatibilityView.swift +++ b/Xcodes/Frontend/InfoPane/CompatibilityView.swift @@ -50,7 +50,6 @@ private struct WrapperView: View { Text("Is Nil?") } } - .animation(.default) .frame(width: 200, height: 100) .padding() } diff --git a/Xcodes/Frontend/InfoPane/CompilersView.swift b/Xcodes/Frontend/InfoPane/CompilersView.swift index 68d2f52..cfa7df0 100644 --- a/Xcodes/Frontend/InfoPane/CompilersView.swift +++ b/Xcodes/Frontend/InfoPane/CompilersView.swift @@ -65,16 +65,13 @@ private struct WrapperView: View { var body: some View { VStack { - HStack { - CompilersView(compilers: compilers) - .border(.red) - } + CompilersView(compilers: compilers) + .border(.red) Spacer() Toggle(isOn: $isNil) { Text("Is Nil?") } } - .animation(.default) .frame(width: 200, height: 100) .padding() } diff --git a/Xcodes/Frontend/InfoPane/IconView.swift b/Xcodes/Frontend/InfoPane/IconView.swift index e7328d2..f32aef5 100644 --- a/Xcodes/Frontend/InfoPane/IconView.swift +++ b/Xcodes/Frontend/InfoPane/IconView.swift @@ -42,22 +42,19 @@ private struct WrapperView: View { @State var isIcon = false var state: XcodeInstallState { isIcon - ? XcodeInstallState.notInstalled - : XcodeInstallState.installed(Path("/Applications/Xcode.app")!) + ? XcodeInstallState.installed(Path("/Applications/Xcode.app")!) + : XcodeInstallState.notInstalled } var body: some View { VStack { - HStack { - IconView(installState: state) - .border(.red) - } + IconView(installState: state) + .border(.red) Spacer() Toggle(isOn: $isIcon) { - Text("Is an Icon?") + Text("Icon?") } } - .animation(.default) .frame(width: 300, height: 100) .padding() } diff --git a/Xcodes/Frontend/InfoPane/IdenticalBuildView.swift b/Xcodes/Frontend/InfoPane/IdenticalBuildView.swift index 3c78eb0..ad3fcd9 100644 --- a/Xcodes/Frontend/InfoPane/IdenticalBuildView.swift +++ b/Xcodes/Frontend/InfoPane/IdenticalBuildView.swift @@ -33,7 +33,6 @@ struct IdenticalBuildsView: View { .font(.subheadline) } } - .frame(maxWidth: .infinity, alignment: .leading) .accessibilityElement() .accessibility(label: Text("IdenticalBuilds")) .accessibility(value: Text(accessibilityDescription)) @@ -67,16 +66,13 @@ private struct WrapperView: View { var body: some View { VStack { - HStack { - IdenticalBuildsView(builds: builds) + IdenticalBuildsView(builds: builds) .border(.red) - } Spacer() Toggle(isOn: $isEmpty) { - Text("Is Empty?") + Text("No Builds?") } } - .animation(.default) .frame(width: 300, height: 100) .padding() } diff --git a/Xcodes/Frontend/InfoPane/InfoPane.swift b/Xcodes/Frontend/InfoPane/InfoPane.swift index e242bcd..a1c6716 100644 --- a/Xcodes/Frontend/InfoPane/InfoPane.swift +++ b/Xcodes/Frontend/InfoPane/InfoPane.swift @@ -27,7 +27,6 @@ struct InfoPane: View { ) case let .installing(installationStep): InstallationStepDetailView(installationStep: installationStep) - .fixedSize(horizontal: false, vertical: true) CancelInstallButton(xcode: xcode) case .installed: InstalledStateButtons(xcode: xcode) @@ -38,7 +37,6 @@ struct InfoPane: View { Group { ReleaseNotesView(url: xcode.releaseNotesURL) ReleaseDateView(date: xcode.releaseDate) - .frame(maxWidth: .infinity, alignment: .leading) IdenticalBuildsView(builds: xcode.identicalBuilds) CompatibilityView(requiredMacOSVersion: xcode.requiredMacOSVersion) SDKsView(sdks: xcode.sdks) @@ -54,23 +52,6 @@ struct InfoPane: View { UnselectedView() } } - - @ViewBuilder - private func downloadFileSize(for xcode: Xcode) -> some View { - // if we've downloaded it no need to show the download size - if let downloadFileSize = xcode.downloadFileSizeString, case .notInstalled = xcode.installState { - VStack(alignment: .leading) { - Text("DownloadSize") - .font(.headline) - .frame(maxWidth: .infinity, alignment: .leading) - Text("\(downloadFileSize)") - .font(.subheadline) - .frame(maxWidth: .infinity, alignment: .leading) - } - } else { - EmptyView() - } - } } struct InfoPane_Previews: PreviewProvider { diff --git a/Xcodes/Frontend/InfoPane/InstallationStepDetailView.swift b/Xcodes/Frontend/InfoPane/InstallationStepDetailView.swift index 863f204..7c16066 100644 --- a/Xcodes/Frontend/InfoPane/InstallationStepDetailView.swift +++ b/Xcodes/Frontend/InfoPane/InstallationStepDetailView.swift @@ -39,10 +39,11 @@ struct InstallDetailView_Previews: PreviewProvider { } ) ) - + InstallationStepDetailView( installationStep: .unarchiving ) } + .padding() } } diff --git a/Xcodes/Frontend/InfoPane/InstalledStateButtons.swift b/Xcodes/Frontend/InfoPane/InstalledStateButtons.swift index de1c73c..241920f 100644 --- a/Xcodes/Frontend/InfoPane/InstalledStateButtons.swift +++ b/Xcodes/Frontend/InfoPane/InstalledStateButtons.swift @@ -38,7 +38,6 @@ struct InstalledStateButtons: View { Spacer() UninstallButton(xcode: xcode) } - } } } diff --git a/Xcodes/Frontend/InfoPane/NotInstalledStateButtons.swift b/Xcodes/Frontend/InfoPane/NotInstalledStateButtons.swift index db81d9d..a0d09ef 100644 --- a/Xcodes/Frontend/InfoPane/NotInstalledStateButtons.swift +++ b/Xcodes/Frontend/InfoPane/NotInstalledStateButtons.swift @@ -22,8 +22,8 @@ struct NotInstalledStateButtons: View { } label: { Text("Install") .help("Install") } + if let size = downloadFileSizeString { - Text("DownloadSize") .font(.headline) Text(size) diff --git a/Xcodes/Frontend/InfoPane/ReleaseDateView.swift b/Xcodes/Frontend/InfoPane/ReleaseDateView.swift index 0695f4d..2c78b70 100644 --- a/Xcodes/Frontend/InfoPane/ReleaseDateView.swift +++ b/Xcodes/Frontend/InfoPane/ReleaseDateView.swift @@ -14,7 +14,7 @@ struct ReleaseDateView: View { var body: some View { if let date = date { VStack(alignment: .leading) { - Text("ReleaseDate (old version)") + Text("ReleaseDate") .font(.headline) Text("\(date, style: .date)") .font(.subheadline) @@ -41,16 +41,13 @@ private struct WrapperView: View { var body: some View { VStack { - HStack { - ReleaseDateView(date: date) - .border(.red) - } + ReleaseDateView(date: date) + .border(.red) Spacer() Toggle(isOn: $isNil) { Text("Is Nil?") } } - .animation(.default) .frame(width: 300, height: 100) .padding() } diff --git a/Xcodes/Frontend/InfoPane/ReleaseNotesView.swift b/Xcodes/Frontend/InfoPane/ReleaseNotesView.swift index a9aa3c1..14e86e8 100644 --- a/Xcodes/Frontend/InfoPane/ReleaseNotesView.swift +++ b/Xcodes/Frontend/InfoPane/ReleaseNotesView.swift @@ -46,16 +46,12 @@ private struct WrapperView: View { var body: some View { VStack { - HStack { - ReleaseNotesView(url: url) - .border(.red) - } + ReleaseNotesView(url: url).border(.red) Spacer() Toggle(isOn: $hasURL) { Text("Has URL?") } } - .animation(.default) .frame(width: 300, height: 100) .padding() } diff --git a/Xcodes/Frontend/InfoPane/SDKsView.swift b/Xcodes/Frontend/InfoPane/SDKsView.swift index f2d3ed9..5d229cb 100644 --- a/Xcodes/Frontend/InfoPane/SDKsView.swift +++ b/Xcodes/Frontend/InfoPane/SDKsView.swift @@ -65,16 +65,12 @@ private struct WrapperView: View { var body: some View { VStack { - HStack { - SDKsView(sdks: sdks) - .border(.red) - } + SDKsView(sdks: sdks).border(.red) Spacer() Toggle(isOn: $isNil) { Text("Is Nil?") } } - .animation(.default) .frame(width: 200, height: 100) .padding() } From a2379583ef78ce9ad8f9dce9ab24d8a8912d8ceb Mon Sep 17 00:00:00 2001 From: Duong Thai Date: Sat, 14 Oct 2023 13:09:58 +0700 Subject: [PATCH 14/17] fix data dependency, preview performance and layout out of `InfoPane` - I move `UnselectedView` out to: 1. narrow data dependency 2. `InfoPane` should only care about displaying data, not whether an `Xcode` is selected --- Xcodes/Frontend/InfoPane/InfoPane.swift | 297 +++++++++++------------- Xcodes/Frontend/MainWindow.swift | 17 +- 2 files changed, 150 insertions(+), 164 deletions(-) diff --git a/Xcodes/Frontend/InfoPane/InfoPane.swift b/Xcodes/Frontend/InfoPane/InfoPane.swift index a1c6716..6b038a4 100644 --- a/Xcodes/Frontend/InfoPane/InfoPane.swift +++ b/Xcodes/Frontend/InfoPane/InfoPane.swift @@ -6,178 +6,153 @@ import struct XCModel.Compilers import struct XCModel.SDKs struct InfoPane: View { - @EnvironmentObject var appState: AppState - let selectedXcodeID: Xcode.ID? + let xcode: Xcode var body: some View { - if let xcode = appState.allXcodes.first(where: { $0.id == selectedXcodeID }) { - ScrollView { - VStack(alignment: .leading, spacing: 16) { - IconView(installState: xcode.installState) - .frame(maxWidth: .infinity, alignment: .center) + ScrollView { + VStack(alignment: .leading, spacing: 16) { + IconView(installState: xcode.installState) + .frame(maxWidth: .infinity, alignment: .center) - Text(verbatim: "Xcode \(xcode.description) \(xcode.version.buildMetadataIdentifiersDisplay)") - .font(.title) + Text(verbatim: "Xcode \(xcode.description) \(xcode.version.buildMetadataIdentifiersDisplay)") + .font(.title) - switch xcode.installState { - case .notInstalled: - NotInstalledStateButtons( - downloadFileSizeString: xcode.downloadFileSizeString, - id: xcode.id - ) - case let .installing(installationStep): - InstallationStepDetailView(installationStep: installationStep) - CancelInstallButton(xcode: xcode) - case .installed: - InstalledStateButtons(xcode: xcode) - } - - Divider() - - Group { - ReleaseNotesView(url: xcode.releaseNotesURL) - ReleaseDateView(date: xcode.releaseDate) - IdenticalBuildsView(builds: xcode.identicalBuilds) - CompatibilityView(requiredMacOSVersion: xcode.requiredMacOSVersion) - SDKsView(sdks: xcode.sdks) - CompilersView(compilers: xcode.compilers) - } - - Spacer() + switch xcode.installState { + case .notInstalled: + NotInstalledStateButtons( + downloadFileSizeString: xcode.downloadFileSizeString, + id: xcode.id + ) + case let .installing(installationStep): + InstallationStepDetailView(installationStep: installationStep) + CancelInstallButton(xcode: xcode) + case .installed: + InstalledStateButtons(xcode: xcode) } - .padding() + + Divider() + + Group { + ReleaseNotesView(url: xcode.releaseNotesURL) + ReleaseDateView(date: xcode.releaseDate) + IdenticalBuildsView(builds: xcode.identicalBuilds) + CompatibilityView(requiredMacOSVersion: xcode.requiredMacOSVersion) + SDKsView(sdks: xcode.sdks) + CompilersView(compilers: xcode.compilers) + } + + Spacer() } - .frame(minWidth: 200, maxWidth: .infinity) - } else { - UnselectedView() } } } struct InfoPane_Previews: PreviewProvider { static var previews: some View { - Group { - InfoPane(selectedXcodeID: Version(major: 12, minor: 3, patch: 0)) - .environmentObject(configure(AppState()) { - $0.allXcodes = [ - .init( - version: Version(major: 12, minor: 3, patch: 0), - installState: .installed(Path("/Applications/Xcode-12.3.0.app")!), - selected: true, - icon: NSWorkspace.shared.icon(forFile: "/Applications/Xcode-12.3.0.app"), - requiredMacOSVersion: "10.15.4", - releaseNotesURL: URL(string: "https://developer.apple.com/documentation/xcode-release-notes/xcode-12_3-release-notes/")!, - releaseDate: Date(), - sdks: SDKs( - macOS: .init(number: "11.1"), - iOS: .init(number: "14.3"), - watchOS: .init(number: "7.3"), - tvOS: .init(number: "14.3") - ), - compilers: Compilers( - gcc: .init(number: "4"), - llvm_gcc: .init(number: "213"), - llvm: .init(number: "2.3"), - clang: .init(number: "7.3"), - swift: .init(number: "5.3.2") - ), - downloadFileSize: 242_342_424 - ), - ] - }) - .previewDisplayName("Populated, Installed, Selected") - - InfoPane(selectedXcodeID: Version(major: 12, minor: 3, patch: 0)) - .environmentObject(configure(AppState()) { - $0.allXcodes = [ - .init( - version: Version(major: 12, minor: 3, patch: 0), - installState: .installed(Path("/Applications/Xcode-12.3.0.app")!), - selected: false, - icon: NSWorkspace.shared.icon(forFile: "/Applications/Xcode-12.3.0.app"), - sdks: SDKs( - macOS: .init(number: "11.1"), - iOS: .init(number: "14.3"), - watchOS: .init(number: "7.3"), - tvOS: .init(number: "14.3") - ), - compilers: Compilers( - gcc: .init(number: "4"), - llvm_gcc: .init(number: "213"), - llvm: .init(number: "2.3"), - clang: .init(number: "7.3"), - swift: .init(number: "5.3.2") - ), - downloadFileSize: 242_342_424 - ), - ] - }) - .previewDisplayName("Populated, Installed, Unselected") - - InfoPane(selectedXcodeID: Version(major: 12, minor: 3, patch: 0)) - .environmentObject(configure(AppState()) { - $0.allXcodes = [ - .init( - version: Version(major: 12, minor: 3, patch: 0), - installState: .notInstalled, - selected: false, - icon: nil, - sdks: SDKs( - macOS: .init(number: "11.1"), - iOS: .init(number: "14.3"), - watchOS: .init(number: "7.3"), - tvOS: .init(number: "14.3") - ), - compilers: Compilers( - gcc: .init(number: "4"), - llvm_gcc: .init(number: "213"), - llvm: .init(number: "2.3"), - clang: .init(number: "7.3"), - swift: .init(number: "5.3.2") - ), - downloadFileSize: 242_342_424 - ), - ] - }) - .previewDisplayName("Populated, Uninstalled") - - InfoPane(selectedXcodeID: Version(major: 12, minor: 3, patch: 1, buildMetadataIdentifiers: ["1234A"])) - .environmentObject(configure(AppState()) { - $0.allXcodes = [ - .init( - version: Version(major: 12, minor: 3, patch: 1, buildMetadataIdentifiers: ["1234A"]), - installState: .installed(Path("/Applications/Xcode-12.3.0.app")!), - selected: false, - icon: nil, - sdks: nil, - compilers: nil - ), - ] - }) - .previewDisplayName("Basic, installed") - - InfoPane(selectedXcodeID: Version(major: 12, minor: 3, patch: 1, buildMetadataIdentifiers: ["1234A"])) - .environmentObject(configure(AppState()) { - $0.allXcodes = [ - .init( - version: Version(major: 12, minor: 3, patch: 1, buildMetadataIdentifiers: ["1234A"]), - installState: .installing(.downloading(progress: configure(Progress(totalUnitCount: 100)) { $0.completedUnitCount = 40; $0.throughput = 232_323_232; $0.fileCompletedCount = 2_323_004; $0.fileTotalCount = 1_193_939_393 })), - selected: false, - icon: nil, - sdks: nil, - compilers: nil - ), - ] - }) - .previewDisplayName("Basic, installing") - - InfoPane(selectedXcodeID: nil) - .environmentObject(configure(AppState()) { - $0.allXcodes = [ - ] - }) - .previewDisplayName("Empty") - } - .frame(maxWidth: 300) + WrapperView() } } + +private struct WrapperView: View { + @State var name: PreviewName = .Populated_Installed_Selected + + var body: some View { + VStack { + InfoPane(xcode: xcode) + .environmentObject(configure(AppState()) { + $0.allXcodes = [xcode] + }) + .border(.red) + .frame(width: 300, height: 400) + Spacer() + Picker("Preview Name", selection: $name) { + ForEach(PreviewName.allCases) { + Text($0.rawValue).tag($0) + } + } + .pickerStyle(.inline) + } + .frame(maxWidth: .infinity) + .padding() + } + + var xcode: Xcode { xcodeDict[name]! } +} + +private enum PreviewName: String, CaseIterable, Identifiable { + case Populated_Installed_Selected + case Populated_Installed_Unselected + case Populated_Uninstalled + case Basic_Installed + case Basic_Installing + + var id: PreviewName { self } +} + +private var xcodeDict: [PreviewName: Xcode] = [ + .Populated_Installed_Selected: .init( + version: _versionNoMeta, + installState: .installed(Path(_path)!), + selected: true, + icon: NSWorkspace.shared.icon(forFile: _path), + requiredMacOSVersion: _requiredMacOSVersion, + releaseNotesURL: URL(string: "https://developer.apple.com/documentation/xcode-release-notes/xcode-12_3-release-notes/")!, + releaseDate: Date(), + sdks: _sdks, + compilers: _compilers, + downloadFileSize: _downloadFileSize + ), + .Populated_Installed_Unselected: .init( + version: _versionNoMeta, + installState: .installed(Path(_path)!), + selected: false, + icon: NSWorkspace.shared.icon(forFile: _path), + sdks: _sdks, + compilers: _compilers, + downloadFileSize: _downloadFileSize + ), + .Populated_Uninstalled: .init( + version: Version(major: 12, minor: 3, patch: 0), + installState: .notInstalled, + selected: false, + icon: nil, + sdks: _sdks, + compilers: _compilers, + downloadFileSize: _downloadFileSize + ), + .Basic_Installed: .init( + version: _versionWithMeta, + installState: .installed(Path(_path)!), + selected: false, + icon: nil, + sdks: nil, + compilers: nil + ), + .Basic_Installing: .init( + version: _versionWithMeta, + installState: .installing(.downloading(progress: configure(Progress(totalUnitCount: 100)) { $0.completedUnitCount = 40; $0.throughput = 232_323_232; $0.fileCompletedCount = 2_323_004; $0.fileTotalCount = 1_193_939_393 })), + selected: false, + icon: nil, + sdks: nil, + compilers: nil + ), +] + +private let _versionNoMeta = Version(major: 12, minor: 3, patch: 0) +private let _versionWithMeta = Version(major: 12, minor: 3, patch: 1, buildMetadataIdentifiers: ["1234A"]) +private let _path = "/Applications/Xcode-12.3.0.app" +private let _requiredMacOSVersion = "10.15.4" +private let _sdks = SDKs( + macOS: .init(number: "11.1"), + iOS: .init(number: "14.3"), + watchOS: .init(number: "7.3"), + tvOS: .init(number: "14.3") +) +private let _compilers = Compilers( + gcc: .init(number: "4"), + llvm_gcc: .init(number: "213"), + llvm: .init(number: "2.3"), + clang: .init(number: "7.3"), + swift: .init(number: "5.3.2") +) +private let _downloadFileSize: Int64 = 242_342_424 diff --git a/Xcodes/Frontend/MainWindow.swift b/Xcodes/Frontend/MainWindow.swift index fcde005..5c74ea4 100644 --- a/Xcodes/Frontend/MainWindow.swift +++ b/Xcodes/Frontend/MainWindow.swift @@ -27,8 +27,15 @@ struct MainWindow: View { } if isShowingInfoPane { - InfoPane(selectedXcodeID: selectedXcodeID) - .frame(minWidth: 300, maxWidth: .infinity) + Group { + if let xcode = xcode { + InfoPane(xcode: xcode) + } else { + UnselectedView() + } + } + .padding() + .frame(minWidth: 300, maxWidth: .infinity) } } .mainToolbar( @@ -59,7 +66,11 @@ struct MainWindow: View { // FB8954571 focusedValue(_:_:) on List row doesn't propagate value to @FocusedValue .focusedValue(\.selectedXcode, SelectedXcode(appState.allXcodes.first { $0.id == selectedXcodeID })) } - + + private var xcode: Xcode? { + appState.allXcodes.first(where: { $0.id == selectedXcodeID }) + } + private var subtitleText: Text { if let lastUpdated = lastUpdated.map(Date.init(timeIntervalSince1970:)) { return Text("\(localizeString("UpdatedAt")) \(lastUpdated, style: .date) \(lastUpdated, style: .time)") From 2f7d45c67ac5c8983abe28c7997f0f63025264dc Mon Sep 17 00:00:00 2001 From: Duong Thai Date: Sat, 14 Oct 2023 14:18:41 +0700 Subject: [PATCH 15/17] move the control section to `InfoPaneControls`, fix the layout to make it self-contain --- Xcodes.xcodeproj/project.pbxproj | 4 ++ Xcodes/Frontend/InfoPane/InfoPane.swift | 28 ++++----- .../Frontend/InfoPane/InfoPaneControls.swift | 62 +++++++++++++++++++ 3 files changed, 79 insertions(+), 15 deletions(-) create mode 100644 Xcodes/Frontend/InfoPane/InfoPaneControls.swift diff --git a/Xcodes.xcodeproj/project.pbxproj b/Xcodes.xcodeproj/project.pbxproj index f0e0f6d..c057546 100644 --- a/Xcodes.xcodeproj/project.pbxproj +++ b/Xcodes.xcodeproj/project.pbxproj @@ -20,6 +20,7 @@ B0403CF82AD991F800137C09 /* UnselectedView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B0403CF72AD991F800137C09 /* UnselectedView.swift */; }; B0403CFA2AD9942A00137C09 /* NotInstalledStateButtons.swift in Sources */ = {isa = PBXBuildFile; fileRef = B0403CF92AD9942A00137C09 /* NotInstalledStateButtons.swift */; }; B0403CFC2AD9A6BF00137C09 /* InstalledStateButtons.swift in Sources */ = {isa = PBXBuildFile; fileRef = B0403CFB2AD9A6BF00137C09 /* InstalledStateButtons.swift */; }; + B0403CFE2ADA712C00137C09 /* InfoPaneControls.swift in Sources */ = {isa = PBXBuildFile; fileRef = B0403CFD2ADA712C00137C09 /* InfoPaneControls.swift */; }; B0C6AD042AD6E65700E64698 /* ReleaseDateView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B0C6AD032AD6E65700E64698 /* ReleaseDateView.swift */; }; B0C6AD0B2AD9178E00E64698 /* IdenticalBuildView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B0C6AD0A2AD9178E00E64698 /* IdenticalBuildView.swift */; }; B0C6AD0D2AD91D7900E64698 /* IconView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B0C6AD0C2AD91D7900E64698 /* IconView.swift */; }; @@ -209,6 +210,7 @@ B0403CF72AD991F800137C09 /* UnselectedView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnselectedView.swift; sourceTree = ""; }; B0403CF92AD9942A00137C09 /* NotInstalledStateButtons.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotInstalledStateButtons.swift; sourceTree = ""; }; B0403CFB2AD9A6BF00137C09 /* InstalledStateButtons.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstalledStateButtons.swift; sourceTree = ""; }; + B0403CFD2ADA712C00137C09 /* InfoPaneControls.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InfoPaneControls.swift; sourceTree = ""; }; B0C6AD032AD6E65700E64698 /* ReleaseDateView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReleaseDateView.swift; sourceTree = ""; }; B0C6AD0A2AD9178E00E64698 /* IdenticalBuildView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IdenticalBuildView.swift; sourceTree = ""; }; B0C6AD0C2AD91D7900E64698 /* IconView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IconView.swift; sourceTree = ""; }; @@ -640,6 +642,7 @@ B0403CF52AD9849E00137C09 /* CompilersView.swift */, B0403CF12AD934B600137C09 /* CompatibilityView.swift */, CAFBDC67259A308B003DCC5A /* InfoPane.swift */, + B0403CFD2ADA712C00137C09 /* InfoPaneControls.swift */, B0403CF72AD991F800137C09 /* UnselectedView.swift */, B0403CF92AD9942A00137C09 /* NotInstalledStateButtons.swift */, B0403CFB2AD9A6BF00137C09 /* InstalledStateButtons.swift */, @@ -895,6 +898,7 @@ 36741BFF291E50F500A85AAE /* FileError.swift in Sources */, CA9FF8872595607900E47BAF /* InstalledXcode.swift in Sources */, B0403CF22AD934B600137C09 /* CompatibilityView.swift in Sources */, + B0403CFE2ADA712C00137C09 /* InfoPaneControls.swift in Sources */, 53CBAB2C263DCC9100410495 /* XcodesAlert.swift in Sources */, CA42DD6E25AEA8B200BC0B0C /* Logger.swift in Sources */, CA61A6E0259835580008926E /* Xcode.swift in Sources */, diff --git a/Xcodes/Frontend/InfoPane/InfoPane.swift b/Xcodes/Frontend/InfoPane/InfoPane.swift index 6b038a4..a42a8f0 100644 --- a/Xcodes/Frontend/InfoPane/InfoPane.swift +++ b/Xcodes/Frontend/InfoPane/InfoPane.swift @@ -17,18 +17,7 @@ struct InfoPane: View { Text(verbatim: "Xcode \(xcode.description) \(xcode.version.buildMetadataIdentifiersDisplay)") .font(.title) - switch xcode.installState { - case .notInstalled: - NotInstalledStateButtons( - downloadFileSizeString: xcode.downloadFileSizeString, - id: xcode.id - ) - case let .installing(installationStep): - InstallationStepDetailView(installationStep: installationStep) - CancelInstallButton(xcode: xcode) - case .installed: - InstalledStateButtons(xcode: xcode) - } + InfoPaneControls(xcode: xcode) Divider() @@ -79,7 +68,7 @@ private struct WrapperView: View { var xcode: Xcode { xcodeDict[name]! } } -private enum PreviewName: String, CaseIterable, Identifiable { +enum PreviewName: String, CaseIterable, Identifiable { case Populated_Installed_Selected case Populated_Installed_Unselected case Populated_Uninstalled @@ -89,7 +78,7 @@ private enum PreviewName: String, CaseIterable, Identifiable { var id: PreviewName { self } } -private var xcodeDict: [PreviewName: Xcode] = [ +var xcodeDict: [PreviewName: Xcode] = [ .Populated_Installed_Selected: .init( version: _versionNoMeta, installState: .installed(Path(_path)!), @@ -130,7 +119,16 @@ private var xcodeDict: [PreviewName: Xcode] = [ ), .Basic_Installing: .init( version: _versionWithMeta, - installState: .installing(.downloading(progress: configure(Progress(totalUnitCount: 100)) { $0.completedUnitCount = 40; $0.throughput = 232_323_232; $0.fileCompletedCount = 2_323_004; $0.fileTotalCount = 1_193_939_393 })), + installState: .installing(.downloading( + progress: configure(Progress()) { + $0.kind = .file + $0.fileOperationKind = .downloading + $0.estimatedTimeRemaining = 123 + $0.totalUnitCount = 11_944_848_484 + $0.completedUnitCount = 848_444_920 + $0.throughput = 9_211_681 + } + )), selected: false, icon: nil, sdks: nil, diff --git a/Xcodes/Frontend/InfoPane/InfoPaneControls.swift b/Xcodes/Frontend/InfoPane/InfoPaneControls.swift new file mode 100644 index 0000000..fb88044 --- /dev/null +++ b/Xcodes/Frontend/InfoPane/InfoPaneControls.swift @@ -0,0 +1,62 @@ +// +// InfoPaneControls.swift +// Xcodes +// +// Created by Duong Thai on 14/10/2023. +// Copyright © 2023 Robots and Pencils. All rights reserved. +// + +import SwiftUI + +struct InfoPaneControls: View { + let xcode: Xcode + + var body: some View { + VStack (alignment: .leading) { + switch xcode.installState { + case .notInstalled: + NotInstalledStateButtons( + downloadFileSizeString: xcode.downloadFileSizeString, + id: xcode.id) + case .installing(let installationStep): + InstallationStepDetailView(installationStep: installationStep) + CancelInstallButton(xcode: xcode) + case .installed(_): + InstalledStateButtons(xcode: xcode) + } + } + } +} + +struct InfoPaneControls_Previews: PreviewProvider { + static var previews: some View { + WrapperView() + } +} + +private struct WrapperView: View { + @State var name: PreviewName = .Populated_Installed_Selected + + var body: some View { + VStack { + InfoPaneControls(xcode: xcode) + .environmentObject(configure(AppState()) { + $0.allXcodes = [xcode] + }) + .border(.red) + .frame(width: 300, height: 400) + Spacer() + Picker("Preview Name", selection: $name) { + ForEach(PreviewName.allCases) { + Text($0.rawValue).tag($0) + } + } + .pickerStyle(.inline) + + } + .frame(maxWidth: .infinity) + .padding() + } + + var xcode: Xcode { xcodeDict[name]! } +} From 2fdc06031c454bcbfecfc5edb89a9cdc17f038e8 Mon Sep 17 00:00:00 2001 From: Duong Thai Date: Sat, 14 Oct 2023 15:01:00 +0700 Subject: [PATCH 16/17] fix `SDKView` logic, hide the view when the content is empty --- Xcodes/Frontend/InfoPane/SDKsView.swift | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/Xcodes/Frontend/InfoPane/SDKsView.swift b/Xcodes/Frontend/InfoPane/SDKsView.swift index 5d229cb..78b99ae 100644 --- a/Xcodes/Frontend/InfoPane/SDKsView.swift +++ b/Xcodes/Frontend/InfoPane/SDKsView.swift @@ -10,19 +10,28 @@ import SwiftUI import struct XCModel.SDKs struct SDKsView: View { - let sdks: SDKs? + let content: String var body: some View { - if let sdks = sdks { + if content.isEmpty { + EmptyView() + } else { VStack(alignment: .leading) { Text("SDKs").font(.headline) - Text(Self.content(from: sdks)).font(.subheadline) + Text(content).font(.subheadline) } - } else { - EmptyView() } } + init(sdks: SDKs?) { + guard let sdks = sdks else { + self.content = "" + return + } + let content = Self.content(from: sdks) + self.content = content + } + static private func content(from sdks: SDKs) -> String { let content: String = [ ("macOS", sdks.macOS), @@ -41,6 +50,7 @@ struct SDKsView: View { // description for each type of compilers return "\($0.0): \(numbers.joined(separator: ", "))" }.joined(separator: "\n") + .trimmingCharacters(in: .whitespaces) return content } @@ -68,7 +78,7 @@ private struct WrapperView: View { SDKsView(sdks: sdks).border(.red) Spacer() Toggle(isOn: $isNil) { - Text("Is Nil?") + Text("Empty Content?") } } .frame(width: 200, height: 100) From a596e5ff6c2d37742ec06b0620e120f44fca24fb Mon Sep 17 00:00:00 2001 From: Duong Thai Date: Fri, 24 Nov 2023 00:15:41 +0700 Subject: [PATCH 17/17] refactor preview code --- Xcodes.xcodeproj/project.pbxproj | 2 +- .../Frontend/InfoPane/CompatibilityView.swift | 31 +-------- Xcodes/Frontend/InfoPane/CompilersView.swift | 43 ++++--------- Xcodes/Frontend/InfoPane/IconView.swift | 40 +++--------- .../InfoPane/IdenticalBuildView.swift | 33 +++------- Xcodes/Frontend/InfoPane/InfoPane.swift | 41 ++++-------- .../Frontend/InfoPane/InfoPaneControls.swift | 46 +++++--------- .../InfoPane/InstallationStepDetailView.swift | 43 +++++++------ .../InfoPane/InstalledStateButtons.swift | 63 +++++++++---------- .../InfoPane/NotInstalledStateButtons.swift | 36 ++--------- .../Frontend/InfoPane/ReleaseDateView.swift | 25 +------- .../Frontend/InfoPane/ReleaseNotesView.swift | 28 ++------- Xcodes/Frontend/InfoPane/SDKsView.swift | 35 +++-------- Xcodes/Frontend/InfoPane/UnselectedView.swift | 8 +-- Xcodes/XcodesApp.swift | 43 ++++++------- 15 files changed, 154 insertions(+), 363 deletions(-) diff --git a/Xcodes.xcodeproj/project.pbxproj b/Xcodes.xcodeproj/project.pbxproj index c057546..4ce7a31 100644 --- a/Xcodes.xcodeproj/project.pbxproj +++ b/Xcodes.xcodeproj/project.pbxproj @@ -9,7 +9,7 @@ /* Begin PBXBuildFile section */ 36741BFD291E4FDB00A85AAE /* DownloadPreferencePane.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36741BFC291E4FDB00A85AAE /* DownloadPreferencePane.swift */; }; 36741BFF291E50F500A85AAE /* FileError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36741BFE291E50F500A85AAE /* FileError.swift */; }; - 536CFDD2263C94DE00026CE0 /* SignedInView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 536CFDD1263C94DE00026CE0 /* SignedInView.swift */; }; + 536CFDD2263C94DE00026CE0 /* SignedInView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 536CFDD1263C94DE00026CE0 /* SignedInView.swift */; }; 536CFDD4263C9A8000026CE0 /* XcodesSheet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 536CFDD3263C9A8000026CE0 /* XcodesSheet.swift */; }; 53CBAB2C263DCC9100410495 /* XcodesAlert.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53CBAB2B263DCC9100410495 /* XcodesAlert.swift */; }; 63EAA4EB259944450046AB8F /* ProgressButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 63EAA4EA259944450046AB8F /* ProgressButton.swift */; }; diff --git a/Xcodes/Frontend/InfoPane/CompatibilityView.swift b/Xcodes/Frontend/InfoPane/CompatibilityView.swift index 9b60998..2cf3b40 100644 --- a/Xcodes/Frontend/InfoPane/CompatibilityView.swift +++ b/Xcodes/Frontend/InfoPane/CompatibilityView.swift @@ -25,32 +25,7 @@ struct CompatibilityView: View { } } -struct CompatibilityView_Preview: PreviewProvider { - static var previews: some View { - WrapperView() - } -} - -private struct WrapperView: View { - @State var isNil = false - var requiredMacOSVersion: String? { - isNil - ? nil - : "10.15.4" - } - - var body: some View { - VStack { - HStack { - CompatibilityView(requiredMacOSVersion: requiredMacOSVersion) - .border(.red) - } - Spacer() - Toggle(isOn: $isNil) { - Text("Is Nil?") - } - } - .frame(width: 200, height: 100) - .padding() - } +#Preview { + CompatibilityView(requiredMacOSVersion: "10.15.4") + .padding() } diff --git a/Xcodes/Frontend/InfoPane/CompilersView.swift b/Xcodes/Frontend/InfoPane/CompilersView.swift index cfa7df0..b08458a 100644 --- a/Xcodes/Frontend/InfoPane/CompilersView.swift +++ b/Xcodes/Frontend/InfoPane/CompilersView.swift @@ -44,36 +44,15 @@ struct CompilersView: View { } } -struct CompilersView_Preview: PreviewProvider { - static var previews: some View { - WrapperView() - } +#Preview { + let compilers = Compilers( + gcc: .init(number: "4"), + llvm_gcc: .init(number: "213"), + llvm: .init(number: "2.3"), + clang: .init(number: "7.3"), + swift: .init(number: "5.3.2") + ) + + return CompilersView(compilers: compilers) + .padding() } - -private struct WrapperView: View { - @State var isNil = false - var compilers: Compilers? { - isNil - ? nil - : Compilers( - gcc: .init(number: "4"), - llvm_gcc: .init(number: "213"), - llvm: .init(number: "2.3"), - clang: .init(number: "7.3"), - swift: .init(number: "5.3.2")) - } - - var body: some View { - VStack { - CompilersView(compilers: compilers) - .border(.red) - Spacer() - Toggle(isOn: $isNil) { - Text("Is Nil?") - } - } - .frame(width: 200, height: 100) - .padding() - } -} - diff --git a/Xcodes/Frontend/InfoPane/IconView.swift b/Xcodes/Frontend/InfoPane/IconView.swift index f32aef5..c8bf32f 100644 --- a/Xcodes/Frontend/InfoPane/IconView.swift +++ b/Xcodes/Frontend/InfoPane/IconView.swift @@ -24,38 +24,14 @@ struct IconView: View { } } -//#Preview { -// Group { -// IconView(path: "/Applications/Xcode.app") -// IconView() -// } -// .padding() -//} - -struct IconView_Preview: PreviewProvider { - static var previews: some View { - WrapperView() - } +#Preview("Installed") { + IconView(installState: XcodeInstallState.installed(Path("/Applications/Xcode.app")!)) + .frame(width: 300, height: 100) + .padding() } -private struct WrapperView: View { - @State var isIcon = false - var state: XcodeInstallState { - isIcon - ? XcodeInstallState.installed(Path("/Applications/Xcode.app")!) - : XcodeInstallState.notInstalled - } - - var body: some View { - VStack { - IconView(installState: state) - .border(.red) - Spacer() - Toggle(isOn: $isIcon) { - Text("Icon?") - } - } - .frame(width: 300, height: 100) - .padding() - } +#Preview("Not Installed") { + IconView(installState: XcodeInstallState.notInstalled) + .frame(width: 300, height: 100) + .padding() } diff --git a/Xcodes/Frontend/InfoPane/IdenticalBuildView.swift b/Xcodes/Frontend/InfoPane/IdenticalBuildView.swift index ad3fcd9..2eac1f0 100644 --- a/Xcodes/Frontend/InfoPane/IdenticalBuildView.swift +++ b/Xcodes/Frontend/InfoPane/IdenticalBuildView.swift @@ -49,31 +49,14 @@ struct IdenticalBuildsView: View { } } -struct IdenticalBuildsView_Preview: PreviewProvider { - static var previews: some View { - WrapperView() - } +let builds: [Version] = [.init(xcodeVersion: "15.0")!, .init(xcodeVersion: "15.1")!] + +#Preview("Has Some Builds") { + IdenticalBuildsView(builds: builds) + .padding() } -private struct WrapperView: View { - @State var isEmpty = false - var builds: [Version] { - isEmpty - ? [] - : [.init(xcodeVersion: "15.0")!, - .init(xcodeVersion: "15.1")!] - } - - var body: some View { - VStack { - IdenticalBuildsView(builds: builds) - .border(.red) - Spacer() - Toggle(isOn: $isEmpty) { - Text("No Builds?") - } - } - .frame(width: 300, height: 100) - .padding() - } +#Preview("No Build") { + IdenticalBuildsView(builds: []) + .padding() } diff --git a/Xcodes/Frontend/InfoPane/InfoPane.swift b/Xcodes/Frontend/InfoPane/InfoPane.swift index a42a8f0..e1215c8 100644 --- a/Xcodes/Frontend/InfoPane/InfoPane.swift +++ b/Xcodes/Frontend/InfoPane/InfoPane.swift @@ -36,36 +36,21 @@ struct InfoPane: View { } } -struct InfoPane_Previews: PreviewProvider { - static var previews: some View { - WrapperView() - } -} +#Preview(PreviewName.allCases[0].rawValue) { makePreviewContent(for: 0) } +#Preview(PreviewName.allCases[1].rawValue) { makePreviewContent(for: 1) } +#Preview(PreviewName.allCases[2].rawValue) { makePreviewContent(for: 2) } +#Preview(PreviewName.allCases[3].rawValue) { makePreviewContent(for: 3) } +#Preview(PreviewName.allCases[4].rawValue) { makePreviewContent(for: 4) } -private struct WrapperView: View { - @State var name: PreviewName = .Populated_Installed_Selected +private func makePreviewContent(for index: Int) -> some View { + let name = PreviewName.allCases[index] - var body: some View { - VStack { - InfoPane(xcode: xcode) - .environmentObject(configure(AppState()) { - $0.allXcodes = [xcode] - }) - .border(.red) - .frame(width: 300, height: 400) - Spacer() - Picker("Preview Name", selection: $name) { - ForEach(PreviewName.allCases) { - Text($0.rawValue).tag($0) - } - } - .pickerStyle(.inline) - } - .frame(maxWidth: .infinity) - .padding() - } - - var xcode: Xcode { xcodeDict[name]! } + return InfoPane(xcode: xcodeDict[name]!) + .environmentObject(configure(AppState()) { + $0.allXcodes = [xcodeDict[name]!] + }) + .frame(width: 300, height: 400) + .padding() } enum PreviewName: String, CaseIterable, Identifiable { diff --git a/Xcodes/Frontend/InfoPane/InfoPaneControls.swift b/Xcodes/Frontend/InfoPane/InfoPaneControls.swift index fb88044..6034a38 100644 --- a/Xcodes/Frontend/InfoPane/InfoPaneControls.swift +++ b/Xcodes/Frontend/InfoPane/InfoPaneControls.swift @@ -28,35 +28,19 @@ struct InfoPaneControls: View { } } -struct InfoPaneControls_Previews: PreviewProvider { - static var previews: some View { - WrapperView() - } -} - -private struct WrapperView: View { - @State var name: PreviewName = .Populated_Installed_Selected - - var body: some View { - VStack { - InfoPaneControls(xcode: xcode) - .environmentObject(configure(AppState()) { - $0.allXcodes = [xcode] - }) - .border(.red) - .frame(width: 300, height: 400) - Spacer() - Picker("Preview Name", selection: $name) { - ForEach(PreviewName.allCases) { - Text($0.rawValue).tag($0) - } - } - .pickerStyle(.inline) - - } - .frame(maxWidth: .infinity) - .padding() - } - - var xcode: Xcode { xcodeDict[name]! } +#Preview(PreviewName.allCases[0].rawValue) { makePreviewContent(for: 0) } +#Preview(PreviewName.allCases[1].rawValue) { makePreviewContent(for: 1) } +#Preview(PreviewName.allCases[2].rawValue) { makePreviewContent(for: 2) } +#Preview(PreviewName.allCases[3].rawValue) { makePreviewContent(for: 3) } +#Preview(PreviewName.allCases[4].rawValue) { makePreviewContent(for: 4) } + +private func makePreviewContent(for index: Int) -> some View { + let name = PreviewName.allCases[index] + + return InfoPaneControls(xcode: xcodeDict[name]!) + .environmentObject(configure(AppState()) { + $0.allXcodes = [xcodeDict[name]!] + }) + .frame(width: 300) + .padding() } diff --git a/Xcodes/Frontend/InfoPane/InstallationStepDetailView.swift b/Xcodes/Frontend/InfoPane/InstallationStepDetailView.swift index 7c16066..f9fb7d8 100644 --- a/Xcodes/Frontend/InfoPane/InstallationStepDetailView.swift +++ b/Xcodes/Frontend/InfoPane/InstallationStepDetailView.swift @@ -24,26 +24,25 @@ struct InstallationStepDetailView: View { } } -struct InstallDetailView_Previews: PreviewProvider { - static var previews: some View { - Group { - InstallationStepDetailView( - installationStep: .downloading( - progress: configure(Progress()) { - $0.kind = .file - $0.fileOperationKind = .downloading - $0.estimatedTimeRemaining = 123 - $0.totalUnitCount = 11944848484 - $0.completedUnitCount = 848444920 - $0.throughput = 9211681 - } - ) - ) - - InstallationStepDetailView( - installationStep: .unarchiving - ) - } - .padding() - } +#Preview("Downloading") { + InstallationStepDetailView( + installationStep: .downloading( + progress: configure(Progress()) { + $0.kind = .file + $0.fileOperationKind = .downloading + $0.estimatedTimeRemaining = 123 + $0.totalUnitCount = 11944848484 + $0.completedUnitCount = 848444920 + $0.throughput = 9211681 + } + ) + ) + .padding() +} + +#Preview("Unarchiving") { + InstallationStepDetailView( + installationStep: .unarchiving + ) + .padding() } diff --git a/Xcodes/Frontend/InfoPane/InstalledStateButtons.swift b/Xcodes/Frontend/InfoPane/InstalledStateButtons.swift index 241920f..3c60224 100644 --- a/Xcodes/Frontend/InfoPane/InstalledStateButtons.swift +++ b/Xcodes/Frontend/InfoPane/InstalledStateButtons.swift @@ -42,38 +42,35 @@ struct InstalledStateButtons: View { } } -struct InstalledStateButtons_Preview: PreviewProvider { - static var previews: some View { - InstalledStateButtons(xcode: Self.xcode) - .environmentObject(configure(AppState()) { - $0.allXcodes = [Self.xcode] - }) - .padding() - .frame(width: 300) - } - - static private let xcode = Xcode( - version: Version(major: 12, minor: 3, patch: 0), - installState: .installed(Path("/Applications/Xcode-12.3.0.app")!), - selected: true, - icon: NSWorkspace.shared.icon(forFile: "/Applications/Xcode-12.3.0.app"), - requiredMacOSVersion: "10.15.4", - releaseNotesURL: URL(string: "https://developer.apple.com/documentation/xcode-release-notes/xcode-12_3-release-notes/")!, - releaseDate: Date(), - sdks: SDKs( - macOS: .init(number: "11.1"), - iOS: .init(number: "14.3"), - watchOS: .init(number: "7.3"), - tvOS: .init(number: "14.3") - ), - compilers: Compilers( - gcc: .init(number: "4"), - llvm_gcc: .init(number: "213"), - llvm: .init(number: "2.3"), - clang: .init(number: "7.3"), - swift: .init(number: "5.3.2") - ), - downloadFileSize: 242342424 - ) +#Preview { + InstalledStateButtons(xcode: xcode) + .environmentObject(configure(AppState()) { + $0.allXcodes = [xcode] + }) + .padding() + .frame(width: 300) } +private let xcode = Xcode( + version: Version(major: 12, minor: 3, patch: 0), + installState: .installed(Path("/Applications/Xcode-12.3.0.app")!), + selected: true, + icon: NSWorkspace.shared.icon(forFile: "/Applications/Xcode-12.3.0.app"), + requiredMacOSVersion: "10.15.4", + releaseNotesURL: URL(string: "https://developer.apple.com/documentation/xcode-release-notes/xcode-12_3-release-notes/")!, + releaseDate: Date(), + sdks: SDKs( + macOS: .init(number: "11.1"), + iOS: .init(number: "14.3"), + watchOS: .init(number: "7.3"), + tvOS: .init(number: "14.3") + ), + compilers: Compilers( + gcc: .init(number: "4"), + llvm_gcc: .init(number: "213"), + llvm: .init(number: "2.3"), + clang: .init(number: "7.3"), + swift: .init(number: "5.3.2") + ), + downloadFileSize: 242342424 +) diff --git a/Xcodes/Frontend/InfoPane/NotInstalledStateButtons.swift b/Xcodes/Frontend/InfoPane/NotInstalledStateButtons.swift index a0d09ef..28a187e 100644 --- a/Xcodes/Frontend/InfoPane/NotInstalledStateButtons.swift +++ b/Xcodes/Frontend/InfoPane/NotInstalledStateButtons.swift @@ -35,34 +35,10 @@ struct NotInstalledStateButtons: View { } } -struct NotInstalledStateButtons_Preview: PreviewProvider { - static var previews: some View { - WrapperView() - } -} - -private struct WrapperView: View { - @State var isSizeNil = false - - var downloadFileSizeString: String? { - isSizeNil - ? nil - : "1,19 GB" - } - - var body: some View { - VStack { - NotInstalledStateButtons( - downloadFileSizeString: downloadFileSizeString, - id: Version(major: 12, minor: 3, patch: 0) - ) - .border(.red) - Spacer() - Toggle(isOn: $isSizeNil) { - Text("Is Size Nil?") - } - } - .frame(width: 200, height: 100) - .padding() - } +#Preview { + NotInstalledStateButtons( + downloadFileSizeString: "1,19 GB", + id: Version(major: 12, minor: 3, patch: 0) + ) + .padding() } diff --git a/Xcodes/Frontend/InfoPane/ReleaseDateView.swift b/Xcodes/Frontend/InfoPane/ReleaseDateView.swift index 2c78b70..c7ee304 100644 --- a/Xcodes/Frontend/InfoPane/ReleaseDateView.swift +++ b/Xcodes/Frontend/InfoPane/ReleaseDateView.swift @@ -29,26 +29,7 @@ struct ReleaseDateView: View { } } -struct ReleaseDateView_Preview: PreviewProvider { - static var previews: some View { - WrapperView() - } -} - -private struct WrapperView: View { - @State var isNil = false - var date: Date? { isNil ? nil : Date() } - - var body: some View { - VStack { - ReleaseDateView(date: date) - .border(.red) - Spacer() - Toggle(isOn: $isNil) { - Text("Is Nil?") - } - } - .frame(width: 300, height: 100) - .padding() - } +#Preview { + ReleaseDateView(date: Date()) + .padding() } diff --git a/Xcodes/Frontend/InfoPane/ReleaseNotesView.swift b/Xcodes/Frontend/InfoPane/ReleaseNotesView.swift index 14e86e8..10e3638 100644 --- a/Xcodes/Frontend/InfoPane/ReleaseNotesView.swift +++ b/Xcodes/Frontend/InfoPane/ReleaseNotesView.swift @@ -30,29 +30,9 @@ struct ReleaseNotesView: View { } } -struct ReleaseNotesView_Preview: PreviewProvider { - static var previews: some View { - WrapperView() - } -} +#Preview { + let url = URL(string: "https://developer.apple.com/documentation/xcode-release-notes/xcode-12_3-release-notes/")! -private struct WrapperView: View { - @State var hasURL = false - var url: URL? { - hasURL - ? nil - : URL(string: "https://developer.apple.com/documentation/xcode-release-notes/xcode-12_3-release-notes/")! - } - - var body: some View { - VStack { - ReleaseNotesView(url: url).border(.red) - Spacer() - Toggle(isOn: $hasURL) { - Text("Has URL?") - } - } - .frame(width: 300, height: 100) - .padding() - } + return ReleaseNotesView(url: url) + .padding() } diff --git a/Xcodes/Frontend/InfoPane/SDKsView.swift b/Xcodes/Frontend/InfoPane/SDKsView.swift index 78b99ae..0d3552e 100644 --- a/Xcodes/Frontend/InfoPane/SDKsView.swift +++ b/Xcodes/Frontend/InfoPane/SDKsView.swift @@ -56,32 +56,13 @@ struct SDKsView: View { } } -struct SDKsView_Preview: PreviewProvider { - static var previews: some View { - WrapperView() - } -} +#Preview { + let sdks = SDKs( + macOS: .init(number: "11.1"), + iOS: .init(number: "14.3"), + watchOS: .init(number: "7.3"), + tvOS: .init(number: "14.3")) -private struct WrapperView: View { - @State var isNil = false - var sdks: SDKs? { - isNil - ? nil - : SDKs(macOS: .init(number: "11.1"), - iOS: .init(number: "14.3"), - watchOS: .init(number: "7.3"), - tvOS: .init(number: "14.3")) - } - - var body: some View { - VStack { - SDKsView(sdks: sdks).border(.red) - Spacer() - Toggle(isOn: $isNil) { - Text("Empty Content?") - } - } - .frame(width: 200, height: 100) - .padding() - } + return SDKsView(sdks: sdks) + .padding() } diff --git a/Xcodes/Frontend/InfoPane/UnselectedView.swift b/Xcodes/Frontend/InfoPane/UnselectedView.swift index f0d5ea4..f84cb06 100644 --- a/Xcodes/Frontend/InfoPane/UnselectedView.swift +++ b/Xcodes/Frontend/InfoPane/UnselectedView.swift @@ -20,9 +20,7 @@ struct UnselectedView: View { } } -struct UnselectedView_Preview: PreviewProvider { - static var previews: some View { - UnselectedView() - .padding() - } +#Preview { + UnselectedView() + .padding() } diff --git a/Xcodes/XcodesApp.swift b/Xcodes/XcodesApp.swift index 76fb1a3..e735bb8 100644 --- a/Xcodes/XcodesApp.swift +++ b/Xcodes/XcodesApp.swift @@ -9,15 +9,15 @@ struct XcodesApp: App { @SwiftUI.Environment(\.openURL) var openURL: OpenURLAction @StateObject private var appState = AppState() @StateObject private var updater = ObservableUpdater() - + var body: some Scene { WindowGroup("Xcodes") { MainWindow() .environmentObject(appState) .environmentObject(updater) - // This is intentionally used on a View, and not on a WindowGroup, + // This is intentionally used on a View, and not on a WindowGroup, // so that it's triggered when an individual window's phase changes instead of all window phases. - // When used on a View it's also invoked on launch, which doesn't occur with a WindowGroup. + // When used on a View it's also invoked on launch, which doesn't occur with a WindowGroup. // FB8954581 ScenePhase read from App doesn't return a value on launch .onChange(of: scenePhase) { newScenePhase in guard !isTesting else { return } @@ -37,7 +37,7 @@ struct XcodesApp: App { updater.checkForUpdates() } } - + CommandGroup(after: CommandGroupPlacement.newItem) { Button("Refresh") { appState.update() @@ -47,33 +47,33 @@ struct XcodesApp: App { } XcodeCommands(appState: appState) - + CommandGroup(replacing: CommandGroupPlacement.help) { Button("Menu.GitHubRepo") { let xcodesRepoURL = URL(string: "https://github.com/RobotsAndPencils/XcodesApp/")! openURL(xcodesRepoURL) } - + Divider() - + Button("Menu.ReportABug") { let bugReportURL = URL(string: "https://github.com/RobotsAndPencils/XcodesApp/issues/new?assignees=&labels=bug&template=bug_report.md&title=")! openURL(bugReportURL) } - + Button("Menu.RequestNewFeature") { let featureRequestURL = URL(string: "https://github.com/RobotsAndPencils/XcodesApp/issues/new?assignees=&labels=enhancement&template=feature_request.md&title=")! openURL(featureRequestURL) } } } - #if os(macOS) - Settings { - PreferencesView() - .environmentObject(appState) - .environmentObject(updater) - } - #endif +#if os(macOS) + Settings { + PreferencesView() + .environmentObject(appState) + .environmentObject(updater) + } +#endif } } @@ -88,7 +88,7 @@ class AppDelegate: NSObject, NSApplicationDelegate { $0.contentView = NSHostingView(rootView: AboutView(showAcknowledgementsWindow: showAcknowledgementsWindow)) $0.isReleasedWhenClosed = false } - + private let acknowledgementsWindow = configure(NSWindow( contentRect: .zero, styleMask: [.closable, .resizable, .miniaturizable, .titled], @@ -103,21 +103,19 @@ class AppDelegate: NSObject, NSApplicationDelegate { /// If we wanted to use only SwiftUI API to do this we could make a new WindowGroup and use openURL and handlesExternalEvents. /// WindowGroup lets the user open more than one window right now, which is a little strange for an About window. /// (It's also weird that the main Xcode list window can be opened more than once, there should only be one.) - /// To work around this, an AppDelegate holds onto a single instance of an NSWindow that is shown here. + /// To work around this, an AppDelegate holds onto a single instance of an NSWindow that is shown here. /// FB8954588 Scene / WindowGroup is missing API to limit the number of windows that can be created func showAboutWindow() { aboutWindow.center() aboutWindow.makeKeyAndOrderFront(nil) } - + func showAcknowledgementsWindow() { acknowledgementsWindow.center() acknowledgementsWindow.makeKeyAndOrderFront(nil) } - - func applicationDidFinishLaunching(_ notification: Notification) { - - } + + func applicationDidFinishLaunching(_: Notification) {} } func localizeString(_ key: String, comment: String = "") -> String { @@ -126,5 +124,4 @@ func localizeString(_ key: String, comment: String = "") -> String { } else { return NSLocalizedString(key, comment: comment) } - }