gitbay/Views/Builds/BuildDetailView.swift
82 lines · 2747 bytes
1import SwiftUI
2
3/// A build: what it was, then what it printed.
4struct BuildDetailView: View {
5
6 @State private var model: BuildDetailViewModel
7
8 init(client: GitbayClient, repo: String, number: Int64) {
9 _model = State(initialValue: BuildDetailViewModel(
10 client: client, repoPath: repo, number: number
11 ))
12 }
13
14 var body: some View {
15 ZStack {
16 Color.clear
17 if let detail = model.state.value {
18 VStack(spacing: 0) {
19 header(detail.build)
20 Divider()
21 log(detail.log)
22 }
23 }
24 }
25 .overlay { LoadStateOverlay(state: model.state) }
26 .navigationTitle("Build #\(model.number)")
27 .navigationBarTitleDisplayMode(.inline)
28 .task { await model.load() }
29 .refreshable { await model.load() }
30 }
31
32 private func header(_ build: Build) -> some View {
33 VStack(alignment: .leading, spacing: 6) {
34 HStack(spacing: 8) {
35 Text(build.job)
36 .font(.gbSans(.subheadline).weight(.medium))
37 GBChip(build.status, build.statusColor)
38 Spacer()
39 }
40 HStack(spacing: 6) {
41 Text(build.ref)
42 Text("·")
43 NavigationLink(value: RepoRoute.commit(repo: model.repoPath, sha: build.sha)) {
44 Text(build.shortSHA).font(.gbMono(.caption))
45 }
46 }
47 .font(.gbSans(.caption))
48 .foregroundStyle(.secondary)
49
50 Text(timing(build))
51 .font(.gbSans(.caption))
52 .foregroundStyle(.secondary)
53 }
54 .frame(maxWidth: .infinity, alignment: .leading)
55 .padding(.horizontal, 16)
56 .padding(.vertical, 12)
57 }
58
59 /// Queued always; finished only once it is.
60 private func timing(_ build: Build) -> String {
61 let queued = build.createdAt.formatted(date: .abbreviated, time: .shortened)
62 guard let finished = build.finishedAt else { return "queued \(queued)" }
63 return "queued \(queued) · finished \(finished.formatted(date: .omitted, time: .shortened))"
64 }
65
66 @ViewBuilder
67 private func log(_ text: String) -> some View {
68 if text.isEmpty {
69 ContentUnavailableView {
70 Label("No log yet", systemImage: "doc.text")
71 }
72 } else {
73 ScrollView([.horizontal, .vertical]) {
74 Text(text)
75 .font(.gbMono(.caption2))
76 .frame(maxWidth: .infinity, alignment: .leading)
77 .padding(12)
78 .textSelection(.enabled)
79 }
80 }
81 }
82}