a native ios client for gitbay

client ios swift

https://gitbay.org

gitbay/Views/Builds/BuildDetailView.swift

ui-smoke
gitbay-ios/gitbay/Views/Builds/BuildDetailView.swift history · blame · raw

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}