krz/hutch
an ios client for sourcehut
clone: git clone https://gitbay.org/krz/hutch.git
v2.8.1: Hutch/Views/Builds/BuildRowView.swift · raw
1import SwiftUI
2
3struct BuildRowView: View {
4 let job: JobSummary
5
6 var body: some View {
7 HStack(spacing: 12) {
8 JobStatusIcon(status: job.status)
9 .frame(width: 28)
10
11 VStack(alignment: .leading, spacing: 4) {
12 Text(job.displayLabel)
13 .font(.subheadline)
14 .lineLimit(1)
15
16 HStack(spacing: 8) {
17 if let image = job.image {
18 Text(image)
19 .font(.caption)
20 .foregroundStyle(.secondary)
21 }
22
23 Spacer()
24
25 Text(job.created.relativeDescription)
26 .font(.caption)
27 .foregroundStyle(.tertiary)
28 }
29
30 if !job.tasks.isEmpty {
31 TaskProgressView(tasks: job.tasks)
32 }
33 }
34 }
35 .padding(.vertical, 2)
36 }
37}
38
39// MARK: - Job Status Icon
40
41struct JobStatusIcon: View {
42 let status: JobStatus
43
44 var body: some View {
45 Image(systemName: iconName)
46 .foregroundStyle(color)
47 .symbolEffect(.pulse, isActive: status == .running)
48 }
49
50 private var iconName: String {
51 switch status {
52 case .success: "checkmark.circle.fill"
53 case .failed, .timeout: "xmark.circle.fill"
54 case .running: "arrow.trianglehead.2.clockwise.rotate.90"
55 case .queued: "clock.fill"
56 case .pending: "circle.dashed"
57 case .cancelled: "minus.circle.fill"
58 }
59 }
60
61 private var color: Color {
62 switch status {
63 case .success: .green
64 case .failed, .timeout: .red
65 case .running: .yellow
66 case .queued: .orange
67 case .pending, .cancelled: .gray
68 }
69 }
70}
71
72// MARK: - Task Progress
73
74struct TaskProgressView: View {
75 let tasks: [JobTaskSummary]
76
77 var body: some View {
78 HStack(spacing: 6) {
79 ProgressView(value: progress, total: 1.0)
80 .tint(progressColor)
81 .frame(maxWidth: 80)
82
83 Text("\(completedCount)/\(tasks.count) tasks")
84 .font(.caption2)
85 .foregroundStyle(.secondary)
86 }
87 }
88
89 private var completedCount: Int {
90 tasks.filter { $0.status == .success }.count
91 }
92
93 private var progress: Double {
94 tasks.isEmpty ? 0 : Double(completedCount) / Double(tasks.count)
95 }
96
97 private var progressColor: Color {
98 if tasks.contains(where: { $0.status == .failed }) {
99 return .red
100 }
101 if completedCount == tasks.count {
102 return .green
103 }
104 return .blue
105 }
106}