redesigned infoPane

This commit is contained in:
Matt Kiazyk 2023-12-19 14:36:34 -06:00
parent 7070575a32
commit ede9bd1c46
19 changed files with 485 additions and 68 deletions

View file

@ -115,7 +115,9 @@
E81D7EA02805250100A205FC /* Collection+.swift in Sources */ = {isa = PBXBuildFile; fileRef = E81D7E9F2805250100A205FC /* Collection+.swift */; };
E832EAF82B0FBCF4001B570D /* RuntimeInstallationStepDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E832EAF72B0FBCF4001B570D /* RuntimeInstallationStepDetailView.swift */; };
E84B7D0D2B296A8900DBDA2B /* NavigationSplitViewWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = E84B7D0C2B296A8900DBDA2B /* NavigationSplitViewWrapper.swift */; };
E84B7D0F2B30986700DBDA2B /* InfoPane2.swift in Sources */ = {isa = PBXBuildFile; fileRef = E84B7D0E2B30986700DBDA2B /* InfoPane2.swift */; };
E84CF8C12B0FEB8300ECA259 /* RuntimesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E84CF8C02B0FEB8300ECA259 /* RuntimesView.swift */; };
E86671272B309D2F0048559A /* PlatformsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E86671262B309D2F0048559A /* PlatformsView.swift */; };
E87AB3C52939B65E00D72F43 /* Hardware.swift in Sources */ = {isa = PBXBuildFile; fileRef = E87AB3C42939B65E00D72F43 /* Hardware.swift */; };
E87DD6EB25D053FA00D86808 /* Progress+.swift in Sources */ = {isa = PBXBuildFile; fileRef = E87DD6EA25D053FA00D86808 /* Progress+.swift */; };
E89342FA25EDCC17007CF557 /* NotificationManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = E89342F925EDCC17007CF557 /* NotificationManager.swift */; };
@ -311,8 +313,10 @@
E81D7E9F2805250100A205FC /* Collection+.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Collection+.swift"; sourceTree = "<group>"; };
E832EAF72B0FBCF4001B570D /* RuntimeInstallationStepDetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RuntimeInstallationStepDetailView.swift; sourceTree = "<group>"; };
E84B7D0C2B296A8900DBDA2B /* NavigationSplitViewWrapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationSplitViewWrapper.swift; sourceTree = "<group>"; };
E84B7D0E2B30986700DBDA2B /* InfoPane2.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InfoPane2.swift; sourceTree = "<group>"; };
E84CF8C02B0FEB8300ECA259 /* RuntimesView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RuntimesView.swift; sourceTree = "<group>"; };
E856BB73291EDD3D00DC438B /* XcodesKit */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = XcodesKit; path = Xcodes/XcodesKit; sourceTree = "<group>"; };
E86671262B309D2F0048559A /* PlatformsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlatformsView.swift; sourceTree = "<group>"; };
E87AB3C42939B65E00D72F43 /* Hardware.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Hardware.swift; sourceTree = "<group>"; };
E87DD6EA25D053FA00D86808 /* Progress+.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Progress+.swift"; sourceTree = "<group>"; };
E89342F925EDCC17007CF557 /* NotificationManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationManager.swift; sourceTree = "<group>"; };
@ -648,6 +652,8 @@
B0C6AD0C2AD91D7900E64698 /* IconView.swift */,
E832EAF72B0FBCF4001B570D /* RuntimeInstallationStepDetailView.swift */,
E84CF8C02B0FEB8300ECA259 /* RuntimesView.swift */,
E84B7D0E2B30986700DBDA2B /* InfoPane2.swift */,
E86671262B309D2F0048559A /* PlatformsView.swift */,
);
path = InfoPane;
sourceTree = "<group>";
@ -911,6 +917,7 @@
CABFA9C12592EEEA00380FEE /* Version+.swift in Sources */,
E8D655C0288DD04700A139C2 /* SelectedActionType.swift in Sources */,
36741BFD291E4FDB00A85AAE /* DownloadPreferencePane.swift in Sources */,
E86671272B309D2F0048559A /* PlatformsView.swift in Sources */,
CA9FF8522595080100E47BAF /* AcknowledgementsView.swift in Sources */,
CABFA9CE2592EEEA00380FEE /* Version+Xcode.swift in Sources */,
CAFBDB912598FE80003DCC5A /* SelectedXcode.swift in Sources */,
@ -922,6 +929,7 @@
E8B20CBF2A2EDEC20057D816 /* SDKs+Xcode.swift in Sources */,
CA9FF877259528CC00E47BAF /* Version+XcodeReleases.swift in Sources */,
CABFAA2D2592FBFC00380FEE /* Configure.swift in Sources */,
E84B7D0F2B30986700DBDA2B /* InfoPane2.swift in Sources */,
E84B7D0D2B296A8900DBDA2B /* NavigationSplitViewWrapper.swift in Sources */,
CA73510D257BFCEF00EA9CF8 /* NSAttributedString+.swift in Sources */,
CAFBDB952598FE96003DCC5A /* FocusedValues.swift in Sources */,

View file

@ -36,6 +36,7 @@ extension AppState {
Task {
do {
let runtimes = try await self.runtimeService.localInstalledRuntimes()
DispatchQueue.main.async {
self.installedRuntimes = runtimes
}

View file

@ -163,7 +163,6 @@ class AppState: ObservableObject {
checkIfHelperIsInstalled()
setupAutoInstallTimer()
setupDefaults()
updateInstalledRuntimes()
}
func setupDefaults() {
@ -410,10 +409,7 @@ class AppState: ObservableObject {
// Check to see if users MacOS is supported
if let requiredMacOSVersion = availableXcode.requiredMacOSVersion {
let split = requiredMacOSVersion.components(separatedBy: ".").compactMap { Int($0) }
let xcodeMinimumMacOSVersion = OperatingSystemVersion(majorVersion: split[safe: 0] ?? 0, minorVersion: split[safe: 1] ?? 0, patchVersion: split[safe: 2] ?? 0)
if !ProcessInfo.processInfo.isOperatingSystemAtLeast(xcodeMinimumMacOSVersion) {
if hasMinSupportedOS(requiredMacOSVersion: requiredMacOSVersion) {
// prompt
self.presentedAlert = .checkMinSupportedVersion(xcode: availableXcode, macOS: ProcessInfo.processInfo.operatingSystemVersion.versionString())
return
@ -428,6 +424,13 @@ class AppState: ObservableObject {
}
}
func hasMinSupportedOS(requiredMacOSVersion: String) -> Bool {
let split = requiredMacOSVersion.components(separatedBy: ".").compactMap { Int($0) }
let xcodeMinimumMacOSVersion = OperatingSystemVersion(majorVersion: split[safe: 0] ?? 0, minorVersion: split[safe: 1] ?? 0, patchVersion: split[safe: 2] ?? 0)
return !ProcessInfo.processInfo.isOperatingSystemAtLeast(xcodeMinimumMacOSVersion)
}
func install(id: Xcode.ID) {
guard let availableXcode = availableXcodes.first(where: { $0.version == id }) else { return }

View file

@ -8,6 +8,8 @@
import Foundation
import struct XCModel.SDKs
import XcodesKit
import SwiftUI
extension SDKs {
/// Loops through all SDK's and returns an array of buildNumbers (to be used to correlate runtimes)
@ -33,3 +35,24 @@ extension SDKs {
return buildNumbers
}
}
extension DownloadableRuntime {
func icon() -> Image {
switch self.platform {
case .iOS:
return Image(systemName: "iphone")
case .macOS:
return Image(systemName: "macwindow")
case .watchOS:
return Image(systemName: "applewatch")
case .tvOS:
return Image(systemName: "appletv")
case .visionOS:
if #available(macOS 14, *) {
return Image(systemName: "visionpro")
} else {
return Image(systemName: "eyeglasses")
}
}
}
}

View file

@ -61,9 +61,10 @@ struct CancelInstallButton: View {
var body: some View {
Button(action: cancelInstall) {
Text("Cancel")
.help(localizeString("StopInstallation"))
Image(systemName: "xmark.circle.fill")
}
.help(localizeString("StopInstallation"))
.buttonStyle(.plain)
}
private func cancelInstall() {
@ -78,9 +79,9 @@ struct CancelRuntimeInstallButton: View {
var body: some View {
Button(action: cancelInstall) {
Text("Cancel")
.help(localizeString("StopInstallation"))
}
Image(systemName: "xmark.circle.fill")
}.help(localizeString("StopInstallation"))
.buttonStyle(.plain)
}
private func cancelInstall() {

View file

@ -9,16 +9,30 @@
import SwiftUI
struct CompatibilityView: View {
@EnvironmentObject var appState: AppState
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)
HStack(alignment: .top){
VStack(alignment: .leading) {
Text("Compatibility")
.font(.headline)
Text(String(format: localizeString("MacOSRequirement"), requiredMacOSVersion))
.font(.subheadline)
.foregroundColor(appState.hasMinSupportedOS(requiredMacOSVersion: requiredMacOSVersion) ? .red : .primary)
}
Spacer()
if appState.hasMinSupportedOS(requiredMacOSVersion: requiredMacOSVersion) {
Image(systemName: "exclamationmark.triangle.fill")
.font(.largeTitle)
}
}
.frame(maxWidth: .infinity, alignment: .leading)
.padding()
.background(.background)
.clipShape(RoundedRectangle(cornerRadius: 5, style: .continuous))
} else {
EmptyView()
}
@ -28,4 +42,5 @@ struct CompatibilityView: View {
#Preview {
CompatibilityView(requiredMacOSVersion: "10.15.4")
.padding()
.environmentObject(AppState())
}

View file

@ -33,6 +33,10 @@ struct IdenticalBuildsView: View {
.font(.subheadline)
}
}
.frame(maxWidth: .infinity, alignment: .leading)
.padding()
.background(.background)
.clipShape(RoundedRectangle(cornerRadius: 5, style: .continuous))
.accessibilityElement()
.accessibility(label: Text("IdenticalBuilds"))
.accessibility(value: Text(accessibilityDescription))

View file

@ -1,4 +1,5 @@
import AppKit
import XcodesKit
import Path
import SwiftUI
import Version
@ -23,7 +24,7 @@ struct InfoPane: View {
Group {
RuntimesView(xcode: xcode)
ReleaseDateView(date: xcode.releaseDate)
ReleaseDateView(date: xcode.releaseDate, url: xcode.releaseNotesURL)
ReleaseNotesView(url: xcode.releaseNotesURL)
IdenticalBuildsView(builds: xcode.identicalBuilds)
CompatibilityView(requiredMacOSVersion: xcode.requiredMacOSVersion)
@ -37,14 +38,14 @@ struct InfoPane: View {
}
}
#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) }
#Preview(XcodePreviewName.allCases[0].rawValue) { makePreviewContent(for: 0) }
#Preview(XcodePreviewName.allCases[1].rawValue) { makePreviewContent(for: 1) }
#Preview(XcodePreviewName.allCases[2].rawValue) { makePreviewContent(for: 2) }
#Preview(XcodePreviewName.allCases[3].rawValue) { makePreviewContent(for: 3) }
#Preview(XcodePreviewName.allCases[4].rawValue) { makePreviewContent(for: 4) }
private func makePreviewContent(for index: Int) -> some View {
let name = PreviewName.allCases[index]
let name = XcodePreviewName.allCases[index]
return InfoPane(xcode: xcodeDict[name]!)
.environmentObject(configure(AppState()) {
$0.allXcodes = [xcodeDict[name]!]
@ -53,17 +54,17 @@ private func makePreviewContent(for index: Int) -> some View {
.padding()
}
enum PreviewName: String, CaseIterable, Identifiable {
enum XcodePreviewName: String, CaseIterable, Identifiable {
case Populated_Installed_Selected
case Populated_Installed_Unselected
case Populated_Uninstalled
case Basic_Installed
case Basic_Installing
var id: PreviewName { self }
var id: XcodePreviewName { self }
}
var xcodeDict: [PreviewName: Xcode] = [
var xcodeDict: [XcodePreviewName: Xcode] = [
.Populated_Installed_Selected: .init(
version: _versionNoMeta,
installState: .installed(Path(_path)!),
@ -121,15 +122,48 @@ var xcodeDict: [PreviewName: Xcode] = [
),
]
var downloadableRuntimes: [DownloadableRuntime] = {
var runtimes = try! JSONDecoder().decode([DownloadableRuntime].self, from: Current.files.contents(atPath: Path.runtimeCacheFile.string)!)
// set iOS to installed
let iOSIndex = runtimes.firstIndex { $0.sdkBuildUpdate == "19E239" }!
var iOSRuntime = runtimes[iOSIndex]
iOSRuntime.installState = .installed
runtimes[iOSIndex] = iOSRuntime
let watchOSIndex = runtimes.firstIndex { $0.sdkBuildUpdate == "20R362" }!
var runtime = runtimes[watchOSIndex]
runtime.installState = .installing(
RuntimeInstallationStep.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
}
)
)
runtimes[watchOSIndex] = runtime
return runtimes
}()
var installedRuntimes: [CoreSimulatorImage] = {
[CoreSimulatorImage(uuid: "85B22F5B-048B-4331-B6E2-F4196D8B7475", path: ["relative" : "file:///Library/Developer/CoreSimulator/Images/85B22F5B-048B-4331-B6E2-F4196D8B7475.dmg"], runtimeInfo: CoreSimulatorRuntimeInfo(build: "19E240"))] // same as iOS in _SDK's
}()
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")
iOS: .init(number: "15.4", "19E239"),
watchOS: .init(number: "7.3", "20R362"),
tvOS: .init(number: "14.3", "20K67"),
visionOS: .init(number: "1.0", "21N5233e")
)
private let _compilers = Compilers(
gcc: .init(number: "4"),

View file

@ -0,0 +1,176 @@
//
// InfoPane2.swift
// Xcodes
//
// Created by Matt Kiazyk on 2023-12-18.
//
import AppKit
import Path
import SwiftUI
import Version
import struct XCModel.Compilers
import struct XCModel.SDKs
struct InfoPane2: View {
let xcode: Xcode
var body: some View {
ScrollView(.vertical) {
HStack(alignment: .top) {
VStack {
VStack(spacing: 5) {
HStack {
IconView(installState: xcode.installState)
Text(verbatim: "Xcode \(xcode.description) \(xcode.version.buildMetadataIdentifiersDisplay)")
.font(.title)
.frame(maxWidth: .infinity, alignment: .leading)
}
InfoPaneControls(xcode: xcode)
}
.frame(maxWidth: .infinity, alignment: .leading)
.padding()
.background(.background)
.clipShape(RoundedRectangle(cornerRadius: 5, style: .continuous))
VStack {
Text("Platforms")
.font(.title3)
.frame(maxWidth: .infinity, alignment: .leading)
PlatformsView(xcode: xcode)
}
.frame(maxWidth: .infinity, alignment: .leading)
.padding()
.background(.background)
.clipShape(RoundedRectangle(cornerRadius: 5, style: .continuous))
}
VStack(alignment: .leading) {
ReleaseDateView(date: xcode.releaseDate, url: xcode.releaseNotesURL)
CompatibilityView(requiredMacOSVersion: xcode.requiredMacOSVersion)
IdenticalBuildsView(builds: xcode.identicalBuilds)
SDKandCompilers
}
.frame(width: 200)
}
}
}
@ViewBuilder
var SDKandCompilers: some View {
VStack(alignment: .leading, spacing: 16) {
SDKsView(sdks: xcode.sdks)
CompilersView(compilers: xcode.compilers)
}
.frame(maxWidth: .infinity, alignment: .leading)
.padding()
.background(.background)
.clipShape(RoundedRectangle(cornerRadius: 5, style: .continuous))
}
}
#Preview(XcodePreviewName.allCases[0].rawValue) { makePreviewContent(for: 0) }
#Preview(XcodePreviewName.allCases[1].rawValue) { makePreviewContent(for: 1) }
#Preview(XcodePreviewName.allCases[2].rawValue) { makePreviewContent(for: 2) }
#Preview(XcodePreviewName.allCases[3].rawValue) { makePreviewContent(for: 3) }
#Preview(XcodePreviewName.allCases[4].rawValue) { makePreviewContent(for: 4) }
private func makePreviewContent(for index: Int) -> some View {
let name = XcodePreviewName.allCases[index]
return InfoPane2(xcode: xcodeDict[name]!)
.environmentObject(configure(AppState()) {
$0.allXcodes = [xcodeDict[name]!]
})
.frame(width: 600, height: 400)
.padding()
}
enum Preview2Name: String, CaseIterable, Identifiable {
case Populated_Installed_Selected
case Populated_Installed_Unselected
case Populated_Uninstalled
case Basic_Installed
case Basic_Installing
var id: Preview2Name { self }
}
var xcodeDict2: [Preview2Name: 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()) {
$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,
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

View file

@ -15,12 +15,18 @@ struct InfoPaneControls: View {
VStack (alignment: .leading) {
switch xcode.installState {
case .notInstalled:
NotInstalledStateButtons(
downloadFileSizeString: xcode.downloadFileSizeString,
id: xcode.id)
HStack {
Spacer()
NotInstalledStateButtons(
downloadFileSizeString: xcode.downloadFileSizeString,
id: xcode.id)
}
case .installing(let installationStep):
InstallationStepDetailView(installationStep: installationStep)
CancelInstallButton(xcode: xcode)
HStack(alignment: .top) {
InstallationStepDetailView(installationStep: installationStep)
CancelInstallButton(xcode: xcode)
}
case .installed(_):
InstalledStateButtons(xcode: xcode)
}
@ -28,14 +34,14 @@ struct InfoPaneControls: View {
}
}
#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) }
#Preview(XcodePreviewName.allCases[0].rawValue) { makePreviewContent(for: 0) }
#Preview(XcodePreviewName.allCases[1].rawValue) { makePreviewContent(for: 1) }
#Preview(XcodePreviewName.allCases[2].rawValue) { makePreviewContent(for: 2) }
#Preview(XcodePreviewName.allCases[3].rawValue) { makePreviewContent(for: 3) }
#Preview(XcodePreviewName.allCases[4].rawValue) { makePreviewContent(for: 4) }
private func makePreviewContent(for index: Int) -> some View {
let name = PreviewName.allCases[index]
let name = XcodePreviewName.allCases[index]
return InfoPaneControls(xcode: xcodeDict[name]!)
.environmentObject(configure(AppState()) {

View file

@ -0,0 +1,104 @@
//
// PlatformsView.swift
// Xcodes
//
// Created by Matt Kiazyk on 2023-12-18.
//
import Foundation
import SwiftUI
import XcodesKit
struct PlatformsView: View {
@EnvironmentObject var appState: AppState
let xcode: Xcode
var body: some View {
let builds = xcode.sdks?.allBuilds()
let runtimes = builds?.flatMap { sdkBuild in
appState.downloadableRuntimes.filter {
$0.sdkBuildUpdate == sdkBuild
}
}
ForEach(runtimes ?? [], id: \.simulatorVersion.buildUpdate) { runtime in
runtimeView(runtime: runtime)
.frame(minWidth: 200)
.padding()
.background(.quinary)
.clipShape(RoundedRectangle(cornerRadius: 5, style: .continuous))
}
}
@ViewBuilder
func runtimeView(runtime: DownloadableRuntime) -> some View {
VStack(spacing: 10) {
HStack {
runtime.icon()
Text("\(runtime.visibleIdentifier)")
.font(.headline)
pathIfAvailable(xcode: xcode, runtime: runtime)
Spacer()
Text(runtime.downloadFileSizeString)
.font(.subheadline)
}
switch runtime.installState {
case .installed:
EmptyView()
case .notInstalled:
// TODO: Update the downloadableRuntimes with the appropriate installState so we don't have to check path awkwardly
if let path = appState.runtimeInstallPath(xcode: xcode, runtime: runtime) {
EmptyView()
} else {
HStack {
Spacer()
DownloadRuntimeButton(runtime: runtime)
}
}
case .installing(let installationStep):
HStack(alignment: .top, spacing: 5){
RuntimeInstallationStepDetailView(installationStep: installationStep)
.fixedSize(horizontal: false, vertical: true)
CancelRuntimeInstallButton(runtime: runtime)
}
}
}
}
@ViewBuilder
func pathIfAvailable(xcode: Xcode, runtime: DownloadableRuntime) -> some View {
if let path = appState.runtimeInstallPath(xcode: xcode, runtime: runtime) {
Button(action: { appState.reveal(path: path.string) }) {
Image(systemName: "arrow.right.circle.fill")
}
.buttonStyle(PlainButtonStyle())
.help("RevealInFinder")
} else {
EmptyView()
}
}
}
#Preview(XcodePreviewName.allCases[0].rawValue) { makePreviewContent(for: 0) }
private func makePreviewContent(for index: Int) -> some View {
let name = XcodePreviewName.allCases[index]
let runtimes = downloadableRuntimes
return PlatformsView(xcode: xcodeDict[name]!)
.environmentObject({ () -> AppState in
let a = AppState()
a.allXcodes = [xcodeDict[name]!]
a.installedRuntimes = installedRuntimes
a.downloadableRuntimes = runtimes
return a
}())
.frame(width: 300)
.padding()
}

View file

@ -10,26 +10,37 @@ import SwiftUI
struct ReleaseDateView: View {
let date: Date?
let url: URL?
var body: some View {
if let date = date {
VStack(alignment: .leading) {
Text("ReleaseDate")
.font(.headline)
Text("\(date, style: .date)")
.font(.subheadline)
}
VStack(alignment: .leading) {
HStack {
Text("ReleaseDate")
.font(.headline)
Spacer()
if let url {
ReleaseNotesView(url: url)
}
}
Text("\(date, style: .date)")
.font(.subheadline)
}
.frame(maxWidth: .infinity, alignment: .leading)
.padding()
.background(.background)
.clipShape(RoundedRectangle(cornerRadius: 5, style: .continuous))
} else {
EmptyView()
}
}
init(date: Date? = nil) {
self.date = date
}
}
#Preview {
ReleaseDateView(date: Date())
ReleaseDateView(date: Date(), url: URL(string: "https://www.xcodes.app")!)
.padding()
}

View file

@ -16,13 +16,13 @@ struct ReleaseNotesView: View {
var body: some View {
if let url = url {
Button(action: { openURL(url) }) {
Label("ReleaseNotes", systemImage: "link")
Image(systemName: "link.circle.fill")
.font(.title)
}
.buttonStyle(LinkButtonStyle())
.buttonStyle(.plain)
.contextMenu(menuItems: {
CopyReleaseNoteButton(url: url)
})
.frame(maxWidth: .infinity, alignment: .leading)
.help("ReleaseNotes.help")
} else {
EmptyView()

View file

@ -20,7 +20,7 @@ struct MainWindow: View {
var body: some View {
NavigationSplitViewWrapper {
XcodeListView(selectedXcodeID: $selectedXcodeID, searchText: searchText, category: category, isInstalledOnly: isInstalledOnly)
.frame(minWidth: 300)
.frame(minWidth: 250)
.layoutPriority(1)
.alert(item: $appState.xcodeBeingConfirmedForUninstallation) { xcode in
Alert(title: Text(String(format: localizeString("Alert.Uninstall.Title"), xcode.description)),
@ -37,7 +37,7 @@ struct MainWindow: View {
} detail: {
Group {
if let xcode = xcode {
InfoPane(xcode: xcode)
InfoPane2(xcode: xcode)
} else {
UnselectedView()
}

View file

@ -70,18 +70,6 @@ struct MainToolbarModifier: ViewModifier {
}
.help("FilterInstalledDescription")
// Button(action: { isShowingInfoPane.toggle() }) {
// if isShowingInfoPane {
// Label("Info", systemImage: "info.circle.fill")
// .foregroundColor(.accentColor)
// } else {
// Label("Info", systemImage: "info.circle")
// }
// }
// .keyboardShortcut(KeyboardShortcut("i", modifiers: [.command, .option]))
// .help("InfoDescription")
}
}
}

View file

@ -335,6 +335,33 @@ For more information, please refer to &lt;<http://unlicense.org/>&gt;\
otherwise be required by Sections 4(a), 4(b) and 4(d) of the License.\
\
\fs34 SwiftUIMasonry\
\
\fs26 MIT License\
\
Copyright (c) 2022 Ciaran O'Brien\
\
Permission is hereby granted, free of charge, to any person obtaining a copy\
of this software and associated documentation files (the "Software"), to deal\
in the Software without restriction, including without limitation the rights\
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\
copies of the Software, and to permit persons to whom the Software is\
furnished to do so, subject to the following conditions:\
\
The above copyright notice and this permission notice shall be included in all\
copies or substantial portions of the Software.\
\
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\
SOFTWARE.\
\
\
\fs34 DockProgress\
\

View file

@ -15467,6 +15467,7 @@
}
},
"ReleaseNotes" : {
"extractionState" : "stale",
"localizations" : {
"ca" : {
"stringUnit" : {

View file

@ -23,6 +23,7 @@ struct XcodesApp: App {
guard !isTesting else { return }
if case .active = newScenePhase {
appState.updateIfNeeded()
appState.updateInstalledRuntimes()
}
}
}

View file

@ -9,14 +9,28 @@ import Foundation
public struct CoreSimulatorPlist: Decodable {
public let images: [CoreSimulatorImage]
public init(images: [CoreSimulatorImage]) {
self.images = images
}
}
public struct CoreSimulatorImage: Decodable {
public let uuid: String
public let path: [String: String]
public let runtimeInfo: CoreSimulatorRuntimeInfo
public init(uuid: String, path: [String : String], runtimeInfo: CoreSimulatorRuntimeInfo) {
self.uuid = uuid
self.path = path
self.runtimeInfo = runtimeInfo
}
}
public struct CoreSimulatorRuntimeInfo: Decodable {
public let build: String
public init(build: String) {
self.build = build
}
}