Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
130 changes: 96 additions & 34 deletions Xcodes/Frontend/XcodeList/InstallationStepRowView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,38 +3,106 @@ import XcodesKit

struct InstallationStepRowView: View {
let installationStep: XcodeInstallationStep
let highlighted: Bool
let cancel: () -> Void

var body: some View {
HStack {
Button(action: cancel) {
switch installationStep {
case let .downloading(progress):
// FB8955769 ProgressView.init(_: Progress) doesn't ensure that changes from the Progress object are applied to the UI on the main thread
// This Progress is vended by URLSession so I don't think we can control that.
// Use our own version of ProgressView that does this instead.
ObservingProgressIndicator(
progress,
controlSize: .small,
style: .spinning
)
DownloadProgressRing(progress, installationStep: installationStep)
case .authenticating, .unarchiving, .moving, .trashingArchive, .checkingSecurity, .finishing:
ProgressView()
.scaleEffect(0.5)
InstallProgressRing(fractionCompleted: nil)
.help(installationStep.stepDescription())
}
}
.buttonStyle(PlainButtonStyle())
.accessibilityLabel(Text("StopInstallation"))
.accessibilityValue(Text(installationStep.stepDescription()))
.frame(minWidth: 65)
}
}

private struct DownloadProgressRing: View {
let installationStep: XcodeInstallationStep
@StateObject private var progress: ObservingProgressIndicator.ProgressWrapper

init(_ progress: Progress, installationStep: XcodeInstallationStep) {
_progress = StateObject(wrappedValue: ObservingProgressIndicator.ProgressWrapper(progress: progress))
self.installationStep = installationStep
}

var body: some View {
InstallProgressRing(
fractionCompleted: progress.progress.isIndeterminate ? nil : progress.progress.fractionCompleted
)
.help(helpText)
}

private var helpText: String {
let percent = progress.progress.fractionCompleted.formatted(.percent.precision(.fractionLength(0)))
let stepDescription = installationStep.stepDescription(message: "\(installationStep.message) (\(percent))")
let details = progress.progress.xcodesLocalizedDescription

return details.isEmpty ? stepDescription : "\(stepDescription)\n\(details)"
}
}

struct InstallProgressRing: View {
@SwiftUI.Environment(\.backgroundProminence) private var backgroundProminence
let fractionCompleted: Double?
var showsStopGlyph = true

private let lineWidth: CGFloat = 2

var body: some View {
ZStack {
ring
.stroke(trackColor, lineWidth: lineWidth)

if let fractionCompleted {
ring
.trim(from: 0, to: fractionCompleted)
.stroke(tintColor, style: StrokeStyle(lineWidth: lineWidth, lineCap: .round))
.rotationEffect(.degrees(-90))
.animation(.easeInOut, value: fractionCompleted)
} else {
TimelineView(.animation) { context in
ring
.trim(from: 0, to: 0.3)
.stroke(tintColor, style: StrokeStyle(lineWidth: lineWidth, lineCap: .round))
.rotationEffect(.degrees(context.date.timeIntervalSinceReferenceDate.truncatingRemainder(dividingBy: 1) * 360))
}
}

Text(String(format: localizeString("InstallationStepDescription"), installationStep.stepNumber, installationStep.stepCount, installationStep.message))
.font(.footnote)

Button(action: cancel) {
Label("Cancel", systemImage: "xmark.circle.fill")
.labelStyle(IconOnlyLabelStyle())

if showsStopGlyph {
RoundedRectangle(cornerRadius: 1.5)
.fill(tintColor)
.frame(width: 6, height: 6)
}
.buttonStyle(PlainButtonStyle())
.foregroundColor(highlighted ? .white : .secondary)
.help("StopInstallation")
}
.frame(minWidth: 80)
.frame(width: 20, height: 20)
.contentShape(Circle())
}

private var ring: some Shape {
Circle().inset(by: lineWidth / 2)
}

private var tintColor: Color {
backgroundProminence == .increased ? .white : .accentColor
}

private var trackColor: Color {
backgroundProminence == .increased ? .white.opacity(0.35) : .secondary.opacity(0.3)
}
}

private extension XcodeInstallationStep {
func stepDescription(message: String? = nil) -> String {
String(format: localizeString("InstallationStepDescription"), stepNumber, stepCount, message ?? self.message)
}
}

Expand All @@ -47,57 +115,51 @@ struct InstallView_Previews: PreviewProvider {
installationStep: .downloading(
progress: configure(Progress(totalUnitCount: 100)) { $0.completedUnitCount = 40 }
),
highlighted: false,
cancel: {}
)

InstallationStepRowView(
installationStep: .unarchiving,
highlighted: false,
cancel: {}
)

InstallationStepRowView(
installationStep: .moving(destination: "/Applications"),
highlighted: false,
cancel: {}
)

InstallationStepRowView(
installationStep: .trashingArchive,
highlighted: false,
cancel: {}
)

InstallationStepRowView(
installationStep: .checkingSecurity,
highlighted: false,
cancel: {}
)

InstallationStepRowView(
installationStep: .finishing,
highlighted: false,
cancel: {}
)
}
.padding()
.background(Color(.windowBackgroundColor))
.environment(\.colorScheme, colorScheme)
}

ForEach(ColorScheme.allCases, id: \.self) { colorScheme in
Group {
InstallationStepRowView(
installationStep: .downloading(
progress: configure(Progress(totalUnitCount: 100)) { $0.completedUnitCount = 40 }
),
highlighted: true,
cancel: {}
)
}
.padding()
.background(Color(.selectedContentBackgroundColor))
.environment(\.backgroundProminence, .increased)
.environment(\.colorScheme, colorScheme)
}
}
Expand Down
1 change: 0 additions & 1 deletion Xcodes/Frontend/XcodeList/XcodeListView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -327,7 +327,6 @@ private struct XcodeVersionGroupRow: View {
case let .installing(installationStep) = installingVersion.installState {
InstallationStepRowView(
installationStep: installationStep,
highlighted: false,
cancel: { appState.presentedAlert = .cancelInstall(xcode: installingVersion) }
)
} else if let latestRelease {
Expand Down
29 changes: 17 additions & 12 deletions Xcodes/Frontend/XcodeList/XcodeListViewRow.swift
Original file line number Diff line number Diff line change
Expand Up @@ -43,11 +43,21 @@ struct XcodeListViewRow: View {
}
}

if case let .installed(path) = xcode.installState {
Text(verbatim: path.string)
.font(.caption)
.foregroundColor(.secondary)
Group {
switch xcode.installState {
case let .installed(path):
Text(verbatim: path.string)
case let .installing(installationStep):
Text(installationStep.message)
case .uninstalling:
Text("Uninstalling")
case .notInstalled:
EmptyView()
}
}
.font(.caption)
.foregroundColor(.secondary)
.lineLimit(1)
}

Spacer()
Expand Down Expand Up @@ -169,17 +179,12 @@ struct XcodeListViewRow: View {
case let .installing(installationStep):
InstallationStepRowView(
installationStep: installationStep,
highlighted: selected,
cancel: { appState.presentedAlert = .cancelInstall(xcode: xcode) }
)
case .uninstalling:
HStack(spacing: 4) {
ProgressView()
.scaleEffect(0.5)
Text("Uninstalling")
.font(.caption)
.foregroundStyle(.secondary)
}
InstallProgressRing(fractionCompleted: nil, showsStopGlyph: false)
.frame(minWidth: 65)
.help("Uninstalling")
}
}

Expand Down
Loading