Skip to content

Commit b968149

Browse files
committed
Add new platforms list in settings
1 parent 3c5f860 commit b968149

19 files changed

Lines changed: 513 additions & 48 deletions

File tree

Xcodes.xcodeproj/project.pbxproj

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,8 @@
116116
E832EAF82B0FBCF4001B570D /* RuntimeInstallationStepDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E832EAF72B0FBCF4001B570D /* RuntimeInstallationStepDetailView.swift */; };
117117
E84B7D0D2B296A8900DBDA2B /* NavigationSplitViewWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = E84B7D0C2B296A8900DBDA2B /* NavigationSplitViewWrapper.swift */; };
118118
E84E4F522B323A5F003F3959 /* CornerRadiusModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = E84E4F512B323A5F003F3959 /* CornerRadiusModifier.swift */; };
119+
E84E4F542B333864003F3959 /* PlatformsListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E84E4F532B333864003F3959 /* PlatformsListView.swift */; };
120+
E84E4F572B335094003F3959 /* OrderedCollections in Frameworks */ = {isa = PBXBuildFile; productRef = E84E4F562B335094003F3959 /* OrderedCollections */; };
119121
E86671272B309D2F0048559A /* PlatformsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E86671262B309D2F0048559A /* PlatformsView.swift */; };
120122
E87AB3C52939B65E00D72F43 /* Hardware.swift in Sources */ = {isa = PBXBuildFile; fileRef = E87AB3C42939B65E00D72F43 /* Hardware.swift */; };
121123
E87DD6EB25D053FA00D86808 /* Progress+.swift in Sources */ = {isa = PBXBuildFile; fileRef = E87DD6EA25D053FA00D86808 /* Progress+.swift */; };
@@ -313,6 +315,7 @@
313315
E832EAF72B0FBCF4001B570D /* RuntimeInstallationStepDetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RuntimeInstallationStepDetailView.swift; sourceTree = "<group>"; };
314316
E84B7D0C2B296A8900DBDA2B /* NavigationSplitViewWrapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationSplitViewWrapper.swift; sourceTree = "<group>"; };
315317
E84E4F512B323A5F003F3959 /* CornerRadiusModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CornerRadiusModifier.swift; sourceTree = "<group>"; };
318+
E84E4F532B333864003F3959 /* PlatformsListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlatformsListView.swift; sourceTree = "<group>"; };
316319
E856BB73291EDD3D00DC438B /* XcodesKit */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = XcodesKit; path = Xcodes/XcodesKit; sourceTree = "<group>"; };
317320
E86671262B309D2F0048559A /* PlatformsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlatformsView.swift; sourceTree = "<group>"; };
318321
E87AB3C42939B65E00D72F43 /* Hardware.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Hardware.swift; sourceTree = "<group>"; };
@@ -352,6 +355,7 @@
352355
E8FD5727291EE4AC001E004C /* AsyncNetworkService in Frameworks */,
353356
CAA1CB2D255A5262003FD669 /* AppleAPI in Frameworks */,
354357
CABFA9EE2592F0CC00380FEE /* SwiftSoup in Frameworks */,
358+
E84E4F572B335094003F3959 /* OrderedCollections in Frameworks */,
355359
E8F44A1E296B4CD7002D6592 /* Path in Frameworks */,
356360
);
357361
runOnlyForDeploymentPostprocessing = 0;
@@ -628,6 +632,7 @@
628632
E8977EA225C11E1500835F80 /* PreferencesView.swift */,
629633
E8DA461025FAF7FB002E85EF /* NotificationsView.swift */,
630634
E8CBDB8A27AE02FF00B22292 /* ExperiementsPreferencePane.swift */,
635+
E84E4F532B333864003F3959 /* PlatformsListView.swift */,
631636
);
632637
path = Preferences;
633638
sourceTree = "<group>";
@@ -705,6 +710,7 @@
705710
E8FD5726291EE4AC001E004C /* AsyncNetworkService */,
706711
E8C0EB19291EF43E0081528A /* XcodesKit */,
707712
E8F44A1D296B4CD7002D6592 /* Path */,
713+
E84E4F562B335094003F3959 /* OrderedCollections */,
708714
);
709715
productName = XcodesMac;
710716
productReference = CAD2E79E2449574E00113D76 /* Xcodes.app */;
@@ -791,6 +797,7 @@
791797
E689540125BE8C64000EBCEA /* XCRemoteSwiftPackageReference "DockProgress" */,
792798
E8FD5725291EE4AC001E004C /* XCRemoteSwiftPackageReference "AsyncHTTPNetworkService" */,
793799
E8F44A1C296B4CD7002D6592 /* XCRemoteSwiftPackageReference "Path" */,
800+
E84E4F552B335094003F3959 /* XCRemoteSwiftPackageReference "swift-collections" */,
794801
);
795802
productRefGroup = CAD2E79F2449574E00113D76 /* Products */;
796803
projectDirPath = "";
@@ -914,6 +921,7 @@
914921
CABFA9C12592EEEA00380FEE /* Version+.swift in Sources */,
915922
E8D655C0288DD04700A139C2 /* SelectedActionType.swift in Sources */,
916923
36741BFD291E4FDB00A85AAE /* DownloadPreferencePane.swift in Sources */,
924+
E84E4F542B333864003F3959 /* PlatformsListView.swift in Sources */,
917925
E86671272B309D2F0048559A /* PlatformsView.swift in Sources */,
918926
CA9FF8522595080100E47BAF /* AcknowledgementsView.swift in Sources */,
919927
CABFA9CE2592EEEA00380FEE /* Version+Xcode.swift in Sources */,
@@ -1501,6 +1509,14 @@
15011509
minimumVersion = 3.2.0;
15021510
};
15031511
};
1512+
E84E4F552B335094003F3959 /* XCRemoteSwiftPackageReference "swift-collections" */ = {
1513+
isa = XCRemoteSwiftPackageReference;
1514+
repositoryURL = "https://github.com/apple/swift-collections.git";
1515+
requirement = {
1516+
kind = upToNextMajorVersion;
1517+
minimumVersion = 1.0.5;
1518+
};
1519+
};
15041520
E8F44A1C296B4CD7002D6592 /* XCRemoteSwiftPackageReference "Path" */ = {
15051521
isa = XCRemoteSwiftPackageReference;
15061522
repositoryURL = "https://github.com/mxcl/Path.swift";
@@ -1572,6 +1588,11 @@
15721588
package = E689540125BE8C64000EBCEA /* XCRemoteSwiftPackageReference "DockProgress" */;
15731589
productName = DockProgress;
15741590
};
1591+
E84E4F562B335094003F3959 /* OrderedCollections */ = {
1592+
isa = XCSwiftPackageProductDependency;
1593+
package = E84E4F552B335094003F3959 /* XCRemoteSwiftPackageReference "swift-collections" */;
1594+
productName = OrderedCollections;
1595+
};
15751596
E8C0EB19291EF43E0081528A /* XcodesKit */ = {
15761597
isa = XCSwiftPackageProductDependency;
15771598
productName = XcodesKit;

Xcodes.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved

Lines changed: 9 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Xcodes/Backend/AppState+Install.swift

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -500,12 +500,13 @@ extension AppState {
500500
}
501501
}
502502

503-
func setInstallationStep(of runtime: DownloadableRuntime, to step: RuntimeInstallationStep) {
503+
func setInstallationStep(of runtime: DownloadableRuntime, to step: RuntimeInstallationStep, postNotification: Bool = true) {
504504
DispatchQueue.main.async {
505505
guard let index = self.downloadableRuntimes.firstIndex(where: { $0.identifier == runtime.identifier }) else { return }
506506
self.downloadableRuntimes[index].installState = .installing(step)
507-
508-
Current.notificationManager.scheduleNotification(title: runtime.name, body: step.description, category: .normal)
507+
if postNotification {
508+
Current.notificationManager.scheduleNotification(title: runtime.name, body: step.description, category: .normal)
509+
}
509510
}
510511
}
511512
}

Xcodes/Backend/AppState+Runtimes.swift

Lines changed: 33 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ extension AppState {
3535
func updateInstalledRuntimes() {
3636
Task {
3737
do {
38+
Logger.appState.info("Loading Installed runtimes")
3839
let runtimes = try await self.runtimeService.localInstalledRuntimes()
3940

4041
DispatchQueue.main.async {
@@ -51,7 +52,7 @@ extension AppState {
5152
do {
5253
let downloadedURL = try await downloadRunTimeFull(runtime: runtime)
5354
if !Task.isCancelled {
54-
Logger.appState.debug("Installing rungtime: \(runtime.name)")
55+
Logger.appState.debug("Installing runtime: \(runtime.name)")
5556
DispatchQueue.main.async {
5657
self.setInstallationStep(of: runtime, to: .installing)
5758
}
@@ -110,11 +111,10 @@ extension AppState {
110111
let aria2Path = Path(url: Bundle.main.url(forAuxiliaryExecutable: "aria2c")!)!
111112
for try await progress in downloadRuntimeWithAria2(runtime, to: expectedRuntimePath, aria2Path: aria2Path) {
112113
DispatchQueue.main.async {
113-
Logger.appState.debug("Downloading: \(progress.fractionCompleted)")
114-
self.setInstallationStep(of: runtime, to: .downloading(progress: progress))
114+
self.setInstallationStep(of: runtime, to: .downloading(progress: progress), postNotification: false)
115115
}
116116
}
117-
Logger.appState.debug("Done downloading")
117+
Logger.appState.debug("Done downloading runtime")
118118

119119
case .urlSession:
120120
throw "Downloading runtimes with URLSession is not supported. Please use aria2"
@@ -210,6 +210,35 @@ extension AppState {
210210

211211
updateInstalledRuntimes()
212212
}
213+
214+
func runtimeInstallPath(xcode: Xcode, runtime: DownloadableRuntime) -> Path? {
215+
if let coreSimulatorInfo = coreSimulatorInfo(runtime: runtime) {
216+
let urlString = coreSimulatorInfo.path["relative"]!
217+
// app was not allowed to open up file:// url's so remove
218+
let fileRemovedString = urlString.replacingOccurrences(of: "file://", with: "")
219+
let url = URL(fileURLWithPath: fileRemovedString)
220+
221+
return Path(url: url)!
222+
}
223+
return nil
224+
}
225+
226+
func coreSimulatorInfo(runtime: DownloadableRuntime) -> CoreSimulatorImage? {
227+
return installedRuntimes.filter({ $0.runtimeInfo.build == runtime.simulatorVersion.buildUpdate }).first
228+
}
229+
230+
func deleteRuntime(runtime: DownloadableRuntime) async throws {
231+
if let info = coreSimulatorInfo(runtime: runtime) {
232+
try await runtimeService.deleteRuntime(identifier: info.uuid)
233+
234+
// give it some time to actually finish deleting before updating
235+
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { [weak self] in
236+
self?.updateInstalledRuntimes()
237+
}
238+
} else {
239+
throw "No simulator found with \(runtime.identifier)"
240+
}
241+
}
213242
}
214243

215244
extension AnyPublisher {

Xcodes/Backend/AppState.swift

Lines changed: 2 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ class AppState: ObservableObject {
4848
@Published var isProcessingAuthRequest = false
4949
@Published var xcodeBeingConfirmedForUninstallation: Xcode?
5050
@Published var presentedAlert: XcodesAlert?
51+
@Published var presentedPreferenceAlert: XcodesPreferencesAlert?
5152
@Published var helperInstallState: HelperInstallState = .notInstalled
5253
/// Whether the user is being prepared for the helper installation alert with an explanation.
5354
/// This closure will be performed after the user chooses whether or not to proceed.
@@ -824,19 +825,7 @@ class AppState: ObservableObject {
824825

825826
self.allXcodes = newAllXcodes.sorted { $0.version > $1.version }
826827
}
827-
828-
// MARK: Runtimes
829-
func runtimeInstallPath(xcode: Xcode, runtime: DownloadableRuntime) -> Path? {
830-
if let coreSimulatorInfo = installedRuntimes.filter({ $0.runtimeInfo.build == runtime.simulatorVersion.buildUpdate }).first {
831-
let urlString = coreSimulatorInfo.path["relative"]!
832-
// app was not allowed to open up file:// url's so remove
833-
let fileRemovedString = urlString.replacingOccurrences(of: "file://", with: "")
834-
let url = URL(fileURLWithPath: fileRemovedString)
835-
836-
return Path(url: url)!
837-
}
838-
return nil
839-
}
828+
840829

841830
// MARK: - Private
842831

Xcodes/Frontend/Common/XcodesAlert.swift

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,3 +18,17 @@ enum XcodesAlert: Identifiable {
1818
}
1919
}
2020
}
21+
22+
// Splitting out alerts that are shown on the preference screen as by default we are showing on the MainWindow()
23+
// and users awkwardly switch screens, sometimes losing the preference screen
24+
enum XcodesPreferencesAlert: Identifiable {
25+
case deletePlatform(runtime: DownloadableRuntime)
26+
case generic(title: String, message: String)
27+
28+
var id: Int {
29+
switch self {
30+
case .deletePlatform: return 1
31+
case .generic: return 2
32+
}
33+
}
34+
}

Xcodes/Frontend/InfoPane/InfoPane.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -171,7 +171,8 @@ var downloadableRuntimes: [DownloadableRuntime] = {
171171
}()
172172

173173
var installedRuntimes: [CoreSimulatorImage] = {
174-
[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
174+
[CoreSimulatorImage(uuid: "85B22F5B-048B-4331-B6E2-F4196D8B7475", path: ["relative" : "file:///Library/Developer/CoreSimulator/Images/85B22F5B-048B-4331-B6E2-F4196D8B7475.dmg"], runtimeInfo: CoreSimulatorRuntimeInfo(build: "19E240")),
175+
CoreSimulatorImage(uuid: "85B22F5B-048B-4331-B6E2-F4196D8B7473", path: ["relative" : "file:///Library/Developer/CoreSimulator/Images/85B22F5B-048B-4331-B6E2-F4196D8B7475.dmg"], runtimeInfo: CoreSimulatorRuntimeInfo(build: "21N5233f"))]
175176
}()
176177

177178

Xcodes/Frontend/InfoPane/PlatformsView.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ struct PlatformsView: View {
6262
HStack(alignment: .top, spacing: 5){
6363
RuntimeInstallationStepDetailView(installationStep: installationStep)
6464
.fixedSize(horizontal: false, vertical: true)
65+
Spacer()
6566
CancelRuntimeInstallButton(runtime: runtime)
6667
}
6768

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
//
2+
// PlatformsListView.swift
3+
// Xcodes
4+
//
5+
// Created by Matt Kiazyk on 2023-12-20.
6+
//
7+
8+
import Foundation
9+
import SwiftUI
10+
import Path
11+
import XcodesKit
12+
import OrderedCollections
13+
14+
struct PlatformsListView: View {
15+
@EnvironmentObject var appState: AppState
16+
@State private var runtimes: OrderedDictionary<DownloadableRuntime.Platform, [DownloadableRuntime]> = [:]
17+
@State private var selectedRuntime: DownloadableRuntime?
18+
19+
var body: some View {
20+
List(selection: $selectedRuntime) {
21+
Text("PlatformsList.Title")
22+
.font(.body)
23+
ForEach(runtimes.elements.sorted(\.key.order), id: \.key) { platform, runtimeList in
24+
Section {
25+
ForEach(runtimeList, id: \.self) { runtime in
26+
HStack {
27+
Text(runtime.name)
28+
Spacer()
29+
Text(runtime.downloadFileSizeString)
30+
Button {
31+
deleteRuntime(runtime: runtime)
32+
} label: {
33+
Image(systemName: "trash")
34+
}
35+
.foregroundStyle(.red)
36+
.buttonStyle(.plain)
37+
}
38+
.frame(height: 30)
39+
}
40+
41+
} header: {
42+
HStack {
43+
runtimeList.first!.icon()
44+
.aspectRatio(contentMode: .fit)
45+
.frame(width: 20)
46+
Text(platform.shortName)
47+
.font(.headline)
48+
}
49+
} footer: {
50+
EmptyView()
51+
}
52+
}
53+
}
54+
.listStyle(.inset(alternatesRowBackgrounds: true))
55+
.task {
56+
loadRuntimes()
57+
}
58+
.onChange(of: appState.installedRuntimes) { _ in
59+
loadRuntimes()
60+
}
61+
}
62+
63+
func loadRuntimes() {
64+
let filteredRuntimes = appState.downloadableRuntimes.filter { runtime in
65+
appState.installedRuntimes.contains { $0.runtimeInfo.build == runtime.simulatorVersion.buildUpdate
66+
}
67+
}
68+
runtimes = OrderedDictionary(grouping: filteredRuntimes, by: { $0.platform })
69+
}
70+
71+
func deleteRuntime(runtime: DownloadableRuntime) {
72+
appState.presentedPreferenceAlert = .deletePlatform(runtime: runtime)
73+
}
74+
}
75+
76+
77+
#Preview {
78+
PlatformsListView()
79+
.environmentObject({ () -> AppState in
80+
let a = AppState()
81+
82+
a.installedRuntimes = installedRuntimes
83+
a.downloadableRuntimes = downloadableRuntimes
84+
85+
return a
86+
87+
}())
88+
}

Xcodes/Frontend/Preferences/PreferencesView.swift

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import SwiftUI
22

33
struct PreferencesView: View {
44
private enum Tabs: Hashable {
5-
case general, updates, advanced, experiment
5+
case general, updates, platforms, advanced, experiment
66
}
77
@EnvironmentObject var appState: AppState
88
@EnvironmentObject var updater: ObservableUpdater
@@ -26,6 +26,12 @@ struct PreferencesView: View {
2626
.tabItem {
2727
Label("Downloads", systemImage: "icloud.and.arrow.down")
2828
}
29+
PlatformsListView()
30+
.environmentObject(appState)
31+
.tabItem {
32+
Label("Platforms", systemImage: "ipad.and.iphone")
33+
}
34+
.tag(Tabs.platforms)
2935
AdvancedPreferencePane()
3036
.environmentObject(appState)
3137
.tabItem {

0 commit comments

Comments
 (0)