80 lines
3.9 KiB
Swift
80 lines
3.9 KiB
Swift
|
|
import SwiftUI
|
||
|
|
|
||
|
|
struct PreflightView: View {
|
||
|
|
let report: PreflightReport
|
||
|
|
let action: String
|
||
|
|
let onProceed: () -> Void
|
||
|
|
let onCancel: () -> Void
|
||
|
|
|
||
|
|
var body: some View {
|
||
|
|
NavigationStack {
|
||
|
|
VStack(spacing: 0) {
|
||
|
|
summaryHeader.padding(.horizontal, 20).padding(.vertical, 16)
|
||
|
|
.background(headerBg)
|
||
|
|
Divider()
|
||
|
|
List(report.checks) { check in
|
||
|
|
HStack(spacing: 10) {
|
||
|
|
Image(systemName: check.icon).foregroundStyle(check.color).font(.system(size: 16)).frame(width: 20)
|
||
|
|
VStack(alignment: .leading, spacing: 2) {
|
||
|
|
Text(check.name).font(.subheadline)
|
||
|
|
Text(check.detail).font(.caption).foregroundStyle(.secondary).lineLimit(2)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
.padding(.vertical, 6)
|
||
|
|
}
|
||
|
|
.listStyle(.plain)
|
||
|
|
Divider()
|
||
|
|
HStack(spacing: 12) {
|
||
|
|
Button("Cancel", action: onCancel).buttonStyle(.bordered).frame(maxWidth: .infinity)
|
||
|
|
if report.canProceed {
|
||
|
|
Button(action: onProceed) {
|
||
|
|
Label(action == "buildAndRun" ? "Build & Run" : "Build",
|
||
|
|
systemImage: action == "buildAndRun" ? "play.fill" : "hammer.fill")
|
||
|
|
.frame(maxWidth: .infinity)
|
||
|
|
}
|
||
|
|
.buttonStyle(.borderedProminent)
|
||
|
|
.tint(report.hasWarnings ? .orange : .green)
|
||
|
|
} else {
|
||
|
|
Button("Fix Issues", action: onCancel)
|
||
|
|
.buttonStyle(.borderedProminent).tint(.red).frame(maxWidth: .infinity)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
.padding(.horizontal, 20).padding(.vertical, 14)
|
||
|
|
.background(Color(.secondarySystemBackground))
|
||
|
|
}
|
||
|
|
.navigationTitle("Pre-Build Checks")
|
||
|
|
.navigationBarTitleDisplayMode(.inline)
|
||
|
|
.toolbar { ToolbarItem(placement: .cancellationAction) { Button("Cancel", action: onCancel) } }
|
||
|
|
}
|
||
|
|
.presentationDetents([.medium, .large])
|
||
|
|
.presentationDragIndicator(.visible)
|
||
|
|
}
|
||
|
|
|
||
|
|
private var summaryHeader: some View {
|
||
|
|
HStack(spacing: 14) {
|
||
|
|
Image(systemName: summaryIcon).font(.system(size: 36, weight: .medium)).foregroundStyle(summaryColor)
|
||
|
|
VStack(alignment: .leading, spacing: 3) {
|
||
|
|
Text(summaryTitle).font(.headline)
|
||
|
|
Text(summarySubtitle).font(.subheadline).foregroundStyle(.secondary)
|
||
|
|
}
|
||
|
|
Spacer()
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
private var summaryIcon: String { !report.canProceed ? "xmark.circle.fill" : report.hasWarnings ? "exclamationmark.triangle.fill" : "checkmark.circle.fill" }
|
||
|
|
private var summaryColor: Color { !report.canProceed ? .red : report.hasWarnings ? .orange : .green }
|
||
|
|
private var summaryTitle: String { !report.canProceed ? "Build Blocked" : report.hasWarnings ? "Ready with Warnings" : "All Checks Passed" }
|
||
|
|
private var headerBg: Color { !report.canProceed ? Color.red.opacity(0.08) : report.hasWarnings ? Color.orange.opacity(0.08) : Color.green.opacity(0.08) }
|
||
|
|
|
||
|
|
private var summarySubtitle: String {
|
||
|
|
let f = report.checks.filter { if case .fail = $0.status { return true }; return false }.count
|
||
|
|
let w = report.checks.filter { if case .warning = $0.status { return true }; return false }.count
|
||
|
|
let p = report.checks.filter { if case .pass = $0.status { return true }; return false }.count
|
||
|
|
var parts: [String] = []
|
||
|
|
if f > 0 { parts.append("\(f) failure\(f == 1 ? "" : "s")") }
|
||
|
|
if w > 0 { parts.append("\(w) warning\(w == 1 ? "" : "s")") }
|
||
|
|
if p > 0 { parts.append("\(p) passed") }
|
||
|
|
return parts.joined(separator: " · ")
|
||
|
|
}
|
||
|
|
}
|