Skip to content

Commit

Permalink
Add new platforms list in settings
Browse files Browse the repository at this point in the history
  • Loading branch information
MattKiazyk committed Dec 20, 2023
1 parent 3c5f860 commit b968149
Show file tree
Hide file tree
Showing 19 changed files with 513 additions and 48 deletions.
21 changes: 21 additions & 0 deletions Xcodes.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,8 @@
E832EAF82B0FBCF4001B570D /* RuntimeInstallationStepDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E832EAF72B0FBCF4001B570D /* RuntimeInstallationStepDetailView.swift */; };
E84B7D0D2B296A8900DBDA2B /* NavigationSplitViewWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = E84B7D0C2B296A8900DBDA2B /* NavigationSplitViewWrapper.swift */; };
E84E4F522B323A5F003F3959 /* CornerRadiusModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = E84E4F512B323A5F003F3959 /* CornerRadiusModifier.swift */; };
E84E4F542B333864003F3959 /* PlatformsListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E84E4F532B333864003F3959 /* PlatformsListView.swift */; };
E84E4F572B335094003F3959 /* OrderedCollections in Frameworks */ = {isa = PBXBuildFile; productRef = E84E4F562B335094003F3959 /* OrderedCollections */; };
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 */; };
Expand Down Expand Up @@ -313,6 +315,7 @@
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>"; };
E84E4F512B323A5F003F3959 /* CornerRadiusModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CornerRadiusModifier.swift; sourceTree = "<group>"; };
E84E4F532B333864003F3959 /* PlatformsListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlatformsListView.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>"; };
Expand Down Expand Up @@ -352,6 +355,7 @@
E8FD5727291EE4AC001E004C /* AsyncNetworkService in Frameworks */,
CAA1CB2D255A5262003FD669 /* AppleAPI in Frameworks */,
CABFA9EE2592F0CC00380FEE /* SwiftSoup in Frameworks */,
E84E4F572B335094003F3959 /* OrderedCollections in Frameworks */,
E8F44A1E296B4CD7002D6592 /* Path in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
Expand Down Expand Up @@ -628,6 +632,7 @@
E8977EA225C11E1500835F80 /* PreferencesView.swift */,
E8DA461025FAF7FB002E85EF /* NotificationsView.swift */,
E8CBDB8A27AE02FF00B22292 /* ExperiementsPreferencePane.swift */,
E84E4F532B333864003F3959 /* PlatformsListView.swift */,
);
path = Preferences;
sourceTree = "<group>";
Expand Down Expand Up @@ -705,6 +710,7 @@
E8FD5726291EE4AC001E004C /* AsyncNetworkService */,
E8C0EB19291EF43E0081528A /* XcodesKit */,
E8F44A1D296B4CD7002D6592 /* Path */,
E84E4F562B335094003F3959 /* OrderedCollections */,
);
productName = XcodesMac;
productReference = CAD2E79E2449574E00113D76 /* Xcodes.app */;
Expand Down Expand Up @@ -791,6 +797,7 @@
E689540125BE8C64000EBCEA /* XCRemoteSwiftPackageReference "DockProgress" */,
E8FD5725291EE4AC001E004C /* XCRemoteSwiftPackageReference "AsyncHTTPNetworkService" */,
E8F44A1C296B4CD7002D6592 /* XCRemoteSwiftPackageReference "Path" */,
E84E4F552B335094003F3959 /* XCRemoteSwiftPackageReference "swift-collections" */,
);
productRefGroup = CAD2E79F2449574E00113D76 /* Products */;
projectDirPath = "";
Expand Down Expand Up @@ -914,6 +921,7 @@
CABFA9C12592EEEA00380FEE /* Version+.swift in Sources */,
E8D655C0288DD04700A139C2 /* SelectedActionType.swift in Sources */,
36741BFD291E4FDB00A85AAE /* DownloadPreferencePane.swift in Sources */,
E84E4F542B333864003F3959 /* PlatformsListView.swift in Sources */,
E86671272B309D2F0048559A /* PlatformsView.swift in Sources */,
CA9FF8522595080100E47BAF /* AcknowledgementsView.swift in Sources */,
CABFA9CE2592EEEA00380FEE /* Version+Xcode.swift in Sources */,
Expand Down Expand Up @@ -1501,6 +1509,14 @@
minimumVersion = 3.2.0;
};
};
E84E4F552B335094003F3959 /* XCRemoteSwiftPackageReference "swift-collections" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/apple/swift-collections.git";
requirement = {
kind = upToNextMajorVersion;
minimumVersion = 1.0.5;
};
};
E8F44A1C296B4CD7002D6592 /* XCRemoteSwiftPackageReference "Path" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/mxcl/Path.swift";
Expand Down Expand Up @@ -1572,6 +1588,11 @@
package = E689540125BE8C64000EBCEA /* XCRemoteSwiftPackageReference "DockProgress" */;
productName = DockProgress;
};
E84E4F562B335094003F3959 /* OrderedCollections */ = {
isa = XCSwiftPackageProductDependency;
package = E84E4F552B335094003F3959 /* XCRemoteSwiftPackageReference "swift-collections" */;
productName = OrderedCollections;
};
E8C0EB19291EF43E0081528A /* XcodesKit */ = {
isa = XCSwiftPackageProductDependency;
productName = XcodesKit;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,15 @@
"version": "2.1.0"
}
},
{
"package": "swift-collections",
"repositoryURL": "https://github.com/apple/swift-collections.git",
"state": {
"branch": null,
"revision": "a902f1823a7ff3c9ab2fba0f992396b948eda307",
"version": "1.0.5"
}
},
{
"package": "SwiftSoup",
"repositoryURL": "https://github.com/scinfu/SwiftSoup",
Expand Down
7 changes: 4 additions & 3 deletions Xcodes/Backend/AppState+Install.swift
Original file line number Diff line number Diff line change
Expand Up @@ -500,12 +500,13 @@ extension AppState {
}
}

func setInstallationStep(of runtime: DownloadableRuntime, to step: RuntimeInstallationStep) {
func setInstallationStep(of runtime: DownloadableRuntime, to step: RuntimeInstallationStep, postNotification: Bool = true) {
DispatchQueue.main.async {
guard let index = self.downloadableRuntimes.firstIndex(where: { $0.identifier == runtime.identifier }) else { return }
self.downloadableRuntimes[index].installState = .installing(step)

Current.notificationManager.scheduleNotification(title: runtime.name, body: step.description, category: .normal)
if postNotification {
Current.notificationManager.scheduleNotification(title: runtime.name, body: step.description, category: .normal)
}
}
}
}
Expand Down
37 changes: 33 additions & 4 deletions Xcodes/Backend/AppState+Runtimes.swift
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ extension AppState {
func updateInstalledRuntimes() {
Task {
do {
Logger.appState.info("Loading Installed runtimes")
let runtimes = try await self.runtimeService.localInstalledRuntimes()

DispatchQueue.main.async {
Expand All @@ -51,7 +52,7 @@ extension AppState {
do {
let downloadedURL = try await downloadRunTimeFull(runtime: runtime)
if !Task.isCancelled {
Logger.appState.debug("Installing rungtime: \(runtime.name)")
Logger.appState.debug("Installing runtime: \(runtime.name)")
DispatchQueue.main.async {
self.setInstallationStep(of: runtime, to: .installing)
}
Expand Down Expand Up @@ -110,11 +111,10 @@ extension AppState {
let aria2Path = Path(url: Bundle.main.url(forAuxiliaryExecutable: "aria2c")!)!
for try await progress in downloadRuntimeWithAria2(runtime, to: expectedRuntimePath, aria2Path: aria2Path) {
DispatchQueue.main.async {
Logger.appState.debug("Downloading: \(progress.fractionCompleted)")
self.setInstallationStep(of: runtime, to: .downloading(progress: progress))
self.setInstallationStep(of: runtime, to: .downloading(progress: progress), postNotification: false)
}
}
Logger.appState.debug("Done downloading")
Logger.appState.debug("Done downloading runtime")

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

updateInstalledRuntimes()
}

func runtimeInstallPath(xcode: Xcode, runtime: DownloadableRuntime) -> Path? {
if let coreSimulatorInfo = coreSimulatorInfo(runtime: runtime) {
let urlString = coreSimulatorInfo.path["relative"]!
// app was not allowed to open up file:// url's so remove
let fileRemovedString = urlString.replacingOccurrences(of: "file://", with: "")
let url = URL(fileURLWithPath: fileRemovedString)

return Path(url: url)!
}
return nil
}

func coreSimulatorInfo(runtime: DownloadableRuntime) -> CoreSimulatorImage? {
return installedRuntimes.filter({ $0.runtimeInfo.build == runtime.simulatorVersion.buildUpdate }).first
}

func deleteRuntime(runtime: DownloadableRuntime) async throws {
if let info = coreSimulatorInfo(runtime: runtime) {
try await runtimeService.deleteRuntime(identifier: info.uuid)

// give it some time to actually finish deleting before updating
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { [weak self] in
self?.updateInstalledRuntimes()
}
} else {
throw "No simulator found with \(runtime.identifier)"
}
}
}

extension AnyPublisher {
Expand Down
15 changes: 2 additions & 13 deletions Xcodes/Backend/AppState.swift
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ class AppState: ObservableObject {
@Published var isProcessingAuthRequest = false
@Published var xcodeBeingConfirmedForUninstallation: Xcode?
@Published var presentedAlert: XcodesAlert?
@Published var presentedPreferenceAlert: XcodesPreferencesAlert?
@Published var helperInstallState: HelperInstallState = .notInstalled
/// Whether the user is being prepared for the helper installation alert with an explanation.
/// This closure will be performed after the user chooses whether or not to proceed.
Expand Down Expand Up @@ -824,19 +825,7 @@ class AppState: ObservableObject {

self.allXcodes = newAllXcodes.sorted { $0.version > $1.version }
}

// MARK: Runtimes
func runtimeInstallPath(xcode: Xcode, runtime: DownloadableRuntime) -> Path? {
if let coreSimulatorInfo = installedRuntimes.filter({ $0.runtimeInfo.build == runtime.simulatorVersion.buildUpdate }).first {
let urlString = coreSimulatorInfo.path["relative"]!
// app was not allowed to open up file:// url's so remove
let fileRemovedString = urlString.replacingOccurrences(of: "file://", with: "")
let url = URL(fileURLWithPath: fileRemovedString)

return Path(url: url)!
}
return nil
}


// MARK: - Private

Expand Down
14 changes: 14 additions & 0 deletions Xcodes/Frontend/Common/XcodesAlert.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,17 @@ enum XcodesAlert: Identifiable {
}
}
}

// Splitting out alerts that are shown on the preference screen as by default we are showing on the MainWindow()
// and users awkwardly switch screens, sometimes losing the preference screen
enum XcodesPreferencesAlert: Identifiable {
case deletePlatform(runtime: DownloadableRuntime)
case generic(title: String, message: String)

var id: Int {
switch self {
case .deletePlatform: return 1
case .generic: return 2
}
}
}
3 changes: 2 additions & 1 deletion Xcodes/Frontend/InfoPane/InfoPane.swift
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,8 @@ var downloadableRuntimes: [DownloadableRuntime] = {
}()

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


Expand Down
1 change: 1 addition & 0 deletions Xcodes/Frontend/InfoPane/PlatformsView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ struct PlatformsView: View {
HStack(alignment: .top, spacing: 5){
RuntimeInstallationStepDetailView(installationStep: installationStep)
.fixedSize(horizontal: false, vertical: true)
Spacer()
CancelRuntimeInstallButton(runtime: runtime)
}

Expand Down
88 changes: 88 additions & 0 deletions Xcodes/Frontend/Preferences/PlatformsListView.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
//
// PlatformsListView.swift
// Xcodes
//
// Created by Matt Kiazyk on 2023-12-20.
//

import Foundation
import SwiftUI
import Path
import XcodesKit
import OrderedCollections

struct PlatformsListView: View {
@EnvironmentObject var appState: AppState
@State private var runtimes: OrderedDictionary<DownloadableRuntime.Platform, [DownloadableRuntime]> = [:]
@State private var selectedRuntime: DownloadableRuntime?

var body: some View {
List(selection: $selectedRuntime) {
Text("PlatformsList.Title")
.font(.body)
ForEach(runtimes.elements.sorted(\.key.order), id: \.key) { platform, runtimeList in
Section {
ForEach(runtimeList, id: \.self) { runtime in
HStack {
Text(runtime.name)
Spacer()
Text(runtime.downloadFileSizeString)
Button {
deleteRuntime(runtime: runtime)
} label: {
Image(systemName: "trash")
}
.foregroundStyle(.red)
.buttonStyle(.plain)
}
.frame(height: 30)
}

} header: {
HStack {
runtimeList.first!.icon()
.aspectRatio(contentMode: .fit)
.frame(width: 20)
Text(platform.shortName)
.font(.headline)
}
} footer: {
EmptyView()
}
}
}
.listStyle(.inset(alternatesRowBackgrounds: true))
.task {
loadRuntimes()
}
.onChange(of: appState.installedRuntimes) { _ in
loadRuntimes()
}
}

func loadRuntimes() {
let filteredRuntimes = appState.downloadableRuntimes.filter { runtime in
appState.installedRuntimes.contains { $0.runtimeInfo.build == runtime.simulatorVersion.buildUpdate
}
}
runtimes = OrderedDictionary(grouping: filteredRuntimes, by: { $0.platform })
}

func deleteRuntime(runtime: DownloadableRuntime) {
appState.presentedPreferenceAlert = .deletePlatform(runtime: runtime)
}
}


#Preview {
PlatformsListView()
.environmentObject({ () -> AppState in
let a = AppState()

a.installedRuntimes = installedRuntimes
a.downloadableRuntimes = downloadableRuntimes

return a

}())
}
8 changes: 7 additions & 1 deletion Xcodes/Frontend/Preferences/PreferencesView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import SwiftUI

struct PreferencesView: View {
private enum Tabs: Hashable {
case general, updates, advanced, experiment
case general, updates, platforms, advanced, experiment
}
@EnvironmentObject var appState: AppState
@EnvironmentObject var updater: ObservableUpdater
Expand All @@ -26,6 +26,12 @@ struct PreferencesView: View {
.tabItem {
Label("Downloads", systemImage: "icloud.and.arrow.down")
}
PlatformsListView()
.environmentObject(appState)
.tabItem {
Label("Platforms", systemImage: "ipad.and.iphone")
}
.tag(Tabs.platforms)
AdvancedPreferencePane()
.environmentObject(appState)
.tabItem {
Expand Down
Loading

0 comments on commit b968149

Please sign in to comment.