Merge pull request #50 from RobotsAndPencils/move-path-into-installed-state

Move path into installed state
This commit is contained in:
Brandon Evans 2021-01-16 13:14:11 -07:00 committed by GitHub
commit 8a65a2e2a2
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 95 additions and 82 deletions

View file

@ -10,6 +10,7 @@
63EAA4EB259944450046AB8F /* ProgressButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 63EAA4EA259944450046AB8F /* ProgressButton.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 */; };
CA378F992466567600A58CE0 /* AppState.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA378F982466567600A58CE0 /* AppState.swift */; };
CA39711924495F0E00AFFB77 /* AppStoreButtonStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA39711824495F0E00AFFB77 /* AppStoreButtonStyle.swift */; };
CA44901F2463AD34003D8213 /* Tag.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA44901E2463AD34003D8213 /* Tag.swift */; };
@ -149,6 +150,7 @@
63EAA4EA259944450046AB8F /* ProgressButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProgressButton.swift; sourceTree = "<group>"; };
CA11E7B92598476C00D2EE1C /* XcodeCommands.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XcodeCommands.swift; sourceTree = "<group>"; };
CA2518EB25A7FF2B00F08414 /* AppStateUpdateTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppStateUpdateTests.swift; sourceTree = "<group>"; };
CA25192925A9644800F08414 /* XcodeInstallState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XcodeInstallState.swift; sourceTree = "<group>"; };
CA378F982466567600A58CE0 /* AppState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppState.swift; sourceTree = "<group>"; };
CA39711824495F0E00AFFB77 /* AppStoreButtonStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppStoreButtonStyle.swift; sourceTree = "<group>"; };
CA44901E2463AD34003D8213 /* Tag.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Tag.swift; sourceTree = "<group>"; };
@ -418,6 +420,7 @@
CA9FF876259528CC00E47BAF /* Version+XcodeReleases.swift */,
CABFA9A62592EEE900380FEE /* Version+Xcode.swift */,
CA61A6DF259835580008926E /* Xcode.swift */,
CA25192925A9644800F08414 /* XcodeInstallState.swift */,
CA11E7B92598476C00D2EE1C /* XcodeCommands.swift */,
);
path = Backend;
@ -739,6 +742,7 @@
CABFA9CE2592EEEA00380FEE /* Version+Xcode.swift in Sources */,
CAFBDB912598FE80003DCC5A /* SelectedXcode.swift in Sources */,
CAA1CB49255A5C97003FD669 /* SignInSMSView.swift in Sources */,
CA25192A25A9644800F08414 /* XcodeInstallState.swift in Sources */,
CAA1CB35255A5AD5003FD669 /* SignInCredentialsView.swift in Sources */,
CA9FF877259528CC00E47BAF /* Version+XcodeReleases.swift in Sources */,
CABFAA2D2592FBFC00380FEE /* Configure.swift in Sources */,

View file

@ -45,7 +45,7 @@ extension AppState {
.handleEvents(receiveOutput: { installedXcode in
DispatchQueue.main.async {
guard let index = self.allXcodes.firstIndex(where: { $0.version.isEquivalent(to: installedXcode.version) }) else { return }
self.allXcodes[index].installState = .installed
self.allXcodes[index].installState = .installed(installedXcode.path)
}
})
.eraseToAnyPublisher()

View file

@ -385,15 +385,14 @@ class AppState: ObservableObject {
})
// If the existing install state is "installing", keep it
let existingXcodeInstallState = allXcodes.first { $0.version == availableXcode.version && $0.installing }?.installState
let existingXcodeInstallState = allXcodes.first { $0.version == availableXcode.version && $0.installState.installing }?.installState
// Otherwise, determine it from whether there's an installed Xcode
let defaultXcodeInstallState: XcodeInstallState = installedXcode != nil ? .installed : .notInstalled
let defaultXcodeInstallState: XcodeInstallState = installedXcode.map { .installed($0.path) } ?? .notInstalled
return Xcode(
version: availableXcode.version,
installState: existingXcodeInstallState ?? defaultXcodeInstallState,
selected: installedXcode != nil && selectedXcodePath?.hasPrefix(installedXcode!.path.string) == true,
path: installedXcode?.path.string,
icon: (installedXcode?.path.string).map(NSWorkspace.shared.icon(forFile:)),
requiredMacOSVersion: availableXcode.requiredMacOSVersion,
releaseNotesURL: availableXcode.releaseNotesURL,
@ -410,9 +409,8 @@ class AppState: ObservableObject {
newAllXcodes.append(
Xcode(
version: installedXcode.version,
installState: .installed,
installState: .installed(installedXcode.path),
selected: selectedXcodePath?.hasPrefix(installedXcode.path.string) == true,
path: installedXcode.path.string,
icon: NSWorkspace.shared.icon(forFile: installedXcode.path.string)
)
)

View file

@ -8,7 +8,6 @@ struct Xcode: Identifiable, CustomStringConvertible {
let version: Version
var installState: XcodeInstallState
let selected: Bool
let path: String?
let icon: NSImage?
let requiredMacOSVersion: String?
let releaseNotesURL: URL?
@ -19,7 +18,6 @@ struct Xcode: Identifiable, CustomStringConvertible {
version: Version,
installState: XcodeInstallState,
selected: Bool,
path: String?,
icon: NSImage?,
requiredMacOSVersion: String? = nil,
releaseNotesURL: URL? = nil,
@ -29,7 +27,6 @@ struct Xcode: Identifiable, CustomStringConvertible {
self.version = version
self.installState = installState
self.selected = selected
self.path = path
self.icon = icon
self.requiredMacOSVersion = requiredMacOSVersion
self.releaseNotesURL = releaseNotesURL
@ -38,21 +35,8 @@ struct Xcode: Identifiable, CustomStringConvertible {
}
var id: Version { version }
var installed: Bool { installState == .installed }
var installing: Bool {
switch installState {
case .installing: return true
default: return false
}
}
var description: String {
version.xcodeDescription
}
}
enum XcodeInstallState: Equatable {
case notInstalled
case installing(InstallationStep)
case installed
}

View file

@ -160,7 +160,7 @@ struct InstallCommand: View {
@FocusedValue(\.selectedXcode) private var selectedXcode: SelectedXcode?
var body: some View {
if selectedXcode.unwrapped?.installing == true {
if selectedXcode.unwrapped?.installState.installing == true {
CancelInstallButton(xcode: selectedXcode.unwrapped)
.keyboardShortcut(".", modifiers: [.command])
} else {
@ -178,7 +178,7 @@ struct SelectCommand: View {
var body: some View {
SelectButton(xcode: selectedXcode.unwrapped)
.keyboardShortcut("s", modifiers: [.command, .option])
.disabled(selectedXcode.unwrapped?.installed != true)
.disabled(selectedXcode.unwrapped?.installState.installed != true)
}
}
@ -189,7 +189,7 @@ struct OpenCommand: View {
var body: some View {
OpenButton(xcode: selectedXcode.unwrapped)
.keyboardShortcut(KeyboardShortcut(.downArrow, modifiers: .command))
.disabled(selectedXcode.unwrapped?.installed != true)
.disabled(selectedXcode.unwrapped?.installState.installed != true)
}
}
@ -200,7 +200,7 @@ struct RevealCommand: View {
var body: some View {
RevealButton(xcode: selectedXcode.unwrapped)
.keyboardShortcut("r", modifiers: [.command, .option])
.disabled(selectedXcode.unwrapped?.installed != true)
.disabled(selectedXcode.unwrapped?.installState.installed != true)
}
}
@ -211,7 +211,7 @@ struct CopyPathCommand: View {
var body: some View {
CopyPathButton(xcode: selectedXcode.unwrapped)
.keyboardShortcut("c", modifiers: [.command, .option])
.disabled(selectedXcode.unwrapped?.installed != true)
.disabled(selectedXcode.unwrapped?.installState.installed != true)
}
}
@ -222,6 +222,6 @@ struct UninstallCommand: View {
var body: some View {
UninstallButton(xcode: selectedXcode.unwrapped)
.keyboardShortcut("u", modifiers: [.command, .option])
.disabled(selectedXcode.unwrapped?.installed != true)
.disabled(selectedXcode.unwrapped?.installState.installed != true)
}
}

View file

@ -0,0 +1,27 @@
import Foundation
import Path
enum XcodeInstallState: Equatable {
case notInstalled
case installing(InstallationStep)
case installed(Path)
var notInstalled: Bool {
switch self {
case .notInstalled: return true
default: return false
}
}
var installing: Bool {
switch self {
case .installing: return true
default: return false
}
}
var installed: Bool {
switch self {
case .installed: return true
default: return false
}
}
}

View file

@ -1,4 +1,5 @@
import AppKit
import Path
import SwiftUI
import Version
import struct XCModel.SDKs
@ -25,28 +26,26 @@ struct InfoPane: View {
InstallButton(xcode: xcode)
case .installing:
CancelInstallButton(xcode: xcode)
case .installed:
if let path = xcode.path {
HStack {
Text(path)
Button(action: { appState.reveal(id: xcode.id) }) {
Image(systemName: "arrow.right.circle.fill")
}
.buttonStyle(PlainButtonStyle())
.help("Reveal in Finder")
case let .installed(path):
HStack {
Text(path.string)
Button(action: { appState.reveal(id: xcode.id) }) {
Image(systemName: "arrow.right.circle.fill")
}
.buttonStyle(PlainButtonStyle())
.help("Reveal in Finder")
}
HStack {
SelectButton(xcode: xcode)
.disabled(xcode.selected)
.help("Selected")
HStack {
SelectButton(xcode: xcode)
.disabled(xcode.selected)
.help("Selected")
OpenButton(xcode: xcode)
.help("Open")
Spacer()
UninstallButton(xcode: xcode)
}
OpenButton(xcode: xcode)
.help("Open")
Spacer()
UninstallButton(xcode: xcode)
}
}
}
@ -70,8 +69,8 @@ struct InfoPane: View {
@ViewBuilder
private func icon(for xcode: Xcode) -> some View {
if let path = xcode.path {
Image(nsImage: NSWorkspace.shared.icon(forFile: path))
if case let .installed(path) = xcode.installState {
Image(nsImage: NSWorkspace.shared.icon(forFile: path.string))
} else {
Image(systemName: "app.fill")
.resizable()
@ -183,9 +182,8 @@ struct InfoPane_Previews: PreviewProvider {
$0.allXcodes = [
.init(
version: Version(major: 12, minor: 3, patch: 0),
installState: .installed,
installState: .installed(Path("/Applications/Xcode-12.3.0.app")!),
selected: true,
path: "/Applications/Xcode-12.3.0.app",
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/")!,
@ -211,9 +209,8 @@ struct InfoPane_Previews: PreviewProvider {
$0.allXcodes = [
.init(
version: Version(major: 12, minor: 3, patch: 0),
installState: .installed,
installState: .installed(Path("/Applications/Xcode-12.3.0.app")!),
selected: false,
path: "/Applications/Xcode-12.3.0.app",
icon: NSWorkspace.shared.icon(forFile: "/Applications/Xcode-12.3.0.app"),
sdks: SDKs(
macOS: .init(number: "11.1"),
@ -239,7 +236,6 @@ struct InfoPane_Previews: PreviewProvider {
version: Version(major: 12, minor: 3, patch: 0),
installState: .notInstalled,
selected: false,
path: nil,
icon: nil,
sdks: SDKs(
macOS: .init(number: "11.1"),
@ -263,9 +259,8 @@ struct InfoPane_Previews: PreviewProvider {
$0.allXcodes = [
.init(
version: Version(major: 12, minor: 3, patch: 0),
installState: .installed,
installState: .installed(Path("/Applications/Xcode-12.3.0.app")!),
selected: false,
path: "/Applications/Xcode-12.3.0.app",
icon: nil,
sdks: nil,
compilers: nil)

View file

@ -1,3 +1,4 @@
import Path
import SwiftUI
import Version
@ -19,7 +20,7 @@ struct XcodeListView: View {
case .all:
xcodes = appState.allXcodes
case .installed:
xcodes = appState.allXcodes.filter { $0.installed }
xcodes = appState.allXcodes.filter { $0.installState.installed }
}
if !searchText.isEmpty {
@ -43,10 +44,10 @@ struct XcodeListView_Previews: PreviewProvider {
.environmentObject({ () -> AppState in
let a = AppState()
a.allXcodes = [
Xcode(version: Version("12.3.0")!, installState: .installed, selected: true, path: "/Applications/Xcode-12.3.0.app", icon: nil),
Xcode(version: Version("12.2.0")!, installState: .notInstalled, selected: false, path: nil, icon: nil),
Xcode(version: Version("12.1.0")!, installState: .installing(.downloading(progress: configure(Progress(totalUnitCount: 100)) { $0.completedUnitCount = 40 })), selected: false, path: nil, icon: nil),
Xcode(version: Version("12.0.0")!, installState: .installed, selected: false, path: "/Applications/Xcode-12.3.0.app", icon: nil),
Xcode(version: Version("12.3.0")!, installState: .installed(Path("/Applications/Xcode-12.3.0.app")!), selected: true, icon: nil),
Xcode(version: Version("12.2.0")!, installState: .notInstalled, selected: false, icon: nil),
Xcode(version: Version("12.1.0")!, installState: .installing(.downloading(progress: configure(Progress(totalUnitCount: 100)) { $0.completedUnitCount = 40 })), selected: false, icon: nil),
Xcode(version: Version("12.0.0")!, installState: .installed(Path("/Applications/Xcode-12.3.0.app")!), selected: false, icon: nil),
]
return a
}())

View file

@ -1,3 +1,4 @@
import Path
import SwiftUI
import Version
@ -14,9 +15,14 @@ struct XcodeListViewRow: View {
Text(xcode.description)
.font(.body)
Text(verbatim: xcode.path ?? "")
.font(.caption)
.foregroundColor(.secondary)
if case let .installed(path) = xcode.installState {
Text(verbatim: path.string)
.font(.caption)
.foregroundColor(.secondary)
} else {
Text(verbatim: "")
.font(.caption)
}
}
Spacer()
@ -55,7 +61,7 @@ struct XcodeListViewRow: View {
@ViewBuilder
private func selectControl(for xcode: Xcode) -> some View {
if xcode.installed {
if xcode.installState.installed {
if xcode.selected {
Image(systemName: "checkmark.circle.fill")
.foregroundColor(.green)
@ -98,22 +104,22 @@ struct XcodeListViewRow_Previews: PreviewProvider {
static var previews: some View {
Group {
XcodeListViewRow(
xcode: Xcode(version: Version("12.3.0")!, installState: .installed, selected: true, path: "/Applications/Xcode-12.3.0.app", icon: nil),
xcode: Xcode(version: Version("12.3.0")!, installState: .installed(Path("/Applications/Xcode-12.3.0.app")!), selected: true, icon: nil),
selected: false
)
XcodeListViewRow(
xcode: Xcode(version: Version("12.2.0")!, installState: .notInstalled, selected: false, path: nil, icon: nil),
xcode: Xcode(version: Version("12.2.0")!, installState: .notInstalled, selected: false, icon: nil),
selected: false
)
XcodeListViewRow(
xcode: Xcode(version: Version("12.1.0")!, installState: .installing(.downloading(progress: configure(Progress(totalUnitCount: 100)) { $0.completedUnitCount = 40 })), selected: false, path: nil, icon: nil),
xcode: Xcode(version: Version("12.1.0")!, installState: .installing(.downloading(progress: configure(Progress(totalUnitCount: 100)) { $0.completedUnitCount = 40 })), selected: false, icon: nil),
selected: false
)
XcodeListViewRow(
xcode: Xcode(version: Version("12.0.0")!, installState: .installed, selected: false, path: "/Applications/Xcode-12.3.0.app", icon: nil),
xcode: Xcode(version: Version("12.0.0")!, installState: .installed(Path("/Applications/Xcode-12.3.0.app")!), selected: false, icon: nil),
selected: false
)
}

View file

@ -67,9 +67,9 @@ class AppStateTests: XCTestCase {
func test_Install_FullHappyPath_Apple() throws {
// Available xcode doesn't necessarily have build identifier
subject.allXcodes = [
.init(version: Version("0.0.0")!, installState: .notInstalled, selected: false, path: nil, icon: nil),
.init(version: Version("0.0.0-Beta.1")!, installState: .notInstalled, selected: false, path: nil, icon: nil),
.init(version: Version("0.0.0-Beta.2")!, installState: .notInstalled, selected: false, path: nil, icon: nil)
.init(version: Version("0.0.0")!, installState: .notInstalled, selected: false, icon: nil),
.init(version: Version("0.0.0-Beta.1")!, installState: .notInstalled, selected: false, icon: nil),
.init(version: Version("0.0.0-Beta.2")!, installState: .notInstalled, selected: false, icon: nil),
]
// It hasn't been downloaded
@ -175,9 +175,9 @@ class AppStateTests: XCTestCase {
func test_Install_FullHappyPath_XcodeReleases() throws {
// Available xcode has build identifier
subject.allXcodes = [
.init(version: Version("0.0.0+ABC123")!, installState: .notInstalled, selected: false, path: nil, icon: nil),
.init(version: Version("0.0.0-Beta.1+DEF456")!, installState: .notInstalled, selected: false, path: nil, icon: nil),
.init(version: Version("0.0.0-Beta.2+GHI789")!, installState: .notInstalled, selected: false, path: nil, icon: nil)
.init(version: Version("0.0.0+ABC123")!, installState: .notInstalled, selected: false, icon: nil),
.init(version: Version("0.0.0-Beta.1+DEF456")!, installState: .notInstalled, selected: false, icon: nil),
.init(version: Version("0.0.0-Beta.2+GHI789")!, installState: .notInstalled, selected: false, icon: nil)
]
// It hasn't been downloaded

View file

@ -13,7 +13,7 @@ class AppStateUpdateTests: XCTestCase {
func testDoesNotReplaceInstallState() throws {
subject.allXcodes = [
Xcode(version: Version("0.0.0")!, installState: .installing(.unarchiving), selected: false, path: nil, icon: nil)
Xcode(version: Version("0.0.0")!, installState: .installing(.unarchiving), selected: false, icon: nil)
]
subject.updateAllXcodes(
@ -30,7 +30,7 @@ class AppStateUpdateTests: XCTestCase {
func testRemovesUninstalledVersion() throws {
subject.allXcodes = [
Xcode(version: Version("0.0.0")!, installState: .installed, selected: true, path: "/Applications/Xcode-0.0.0.app", icon: NSImage(systemSymbolName: "app.fill", accessibilityDescription: nil))
Xcode(version: Version("0.0.0")!, installState: .installed(Path("/Applications/Xcode-0.0.0.app")!), selected: true, icon: NSImage(systemSymbolName: "app.fill", accessibilityDescription: nil))
]
subject.updateAllXcodes(
@ -61,9 +61,8 @@ class AppStateUpdateTests: XCTestCase {
)
XCTAssertEqual(subject.allXcodes[0].version, Version("0.0.0+ABC123")!)
XCTAssertEqual(subject.allXcodes[0].installState, .installed)
XCTAssertEqual(subject.allXcodes[0].installState, .installed(Path("/Applications/Xcode-0.0.0.app")!))
XCTAssertEqual(subject.allXcodes[0].selected, false)
XCTAssertEqual(subject.allXcodes[0].path, "/Applications/Xcode-0.0.0.app")
}
func testAdjustedVersionsAreUsedToLookupAvailableXcode() throws {
@ -82,9 +81,8 @@ class AppStateUpdateTests: XCTestCase {
)
XCTAssertEqual(subject.allXcodes[0].version, Version("0.0.0+ABC123")!)
XCTAssertEqual(subject.allXcodes[0].installState, .installed)
XCTAssertEqual(subject.allXcodes[0].installState, .installed(Path("/Applications/Xcode-0.0.0.app")!))
XCTAssertEqual(subject.allXcodes[0].selected, false)
XCTAssertEqual(subject.allXcodes[0].path, "/Applications/Xcode-0.0.0.app")
// XCModel types aren't equatable, so just check for non-nil for now
XCTAssertNotNil(subject.allXcodes[0].sdks)
}