gitbay/Views/Repos/CommitView.swift
82 lines · 3614 bytes
1import SwiftUI
2
3/// One commit: what it says, who signed it, whether it passed, and what
4/// it changed.
5struct CommitView: View {
6
7 @State private var model: CommitDetailViewModel
8
9 init(client: GitbayClient, repo: String, sha: String) {
10 _model = State(initialValue: CommitDetailViewModel(
11 client: client, repoPath: repo, sha: sha
12 ))
13 }
14
15 var body: some View {
16 List {
17 if let commit = model.state.value {
18 Section {
19 VStack(alignment: .leading, spacing: 6) {
20 Text(commit.subject)
21 .font(.gbSans(.headline))
22 if let message = commit.message,
23 !message.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
24 Text(message.trimmingCharacters(in: .whitespacesAndNewlines))
25 .font(.gbSans(.subheadline))
26 .foregroundStyle(.secondary)
27 }
28 HStack(spacing: 6) {
29 Text(commit.shortSHA)
30 .font(.gbMono(.caption))
31 .foregroundStyle(.secondary)
32 SignatureBadge(signature: commit.signature)
33 }
34 HStack(spacing: 6) {
35 Text(commit.authorName)
36 Text(commit.date, format: .dateTime.year().month().day())
37 .foregroundStyle(.tertiary)
38 }
39 .font(.gbSans(.caption))
40 .foregroundStyle(.secondary)
41 }
42 .padding(.vertical, 2)
43 }
44
45 if let checks = commit.checks, !checks.isEmpty {
46 Section("Checks") {
47 ForEach(checks) { check in
48 HStack {
49 Image(systemName: check.state == "success"
50 ? "checkmark.circle.fill"
51 : check.state == "pending" ? "circle.dotted" : "xmark.circle.fill")
52 .foregroundStyle(check.state == "success"
53 ? Color.gbOK
54 : check.state == "pending" ? Color.gbWarn : Color.gbBad)
55 Text(check.context)
56 .font(.gbSans(.subheadline))
57 Spacer()
58 Text(check.state)
59 .font(.gbSans(.caption))
60 .foregroundStyle(.secondary)
61 }
62 }
63 }
64 }
65
66 if let diff = model.diff, !diff.files.isEmpty {
67 // The patch, in place. The web's commit page is the
68 // diff — a file header expands its hunks rather than
69 // linking away to the whole file.
70 ForEach(diff.files) { file in
71 DiffFileSection(file: file)
72 }
73 }
74 }
75 }
76 .overlay { LoadStateOverlay(state: model.state) }
77 .navigationTitle(String(model.sha.prefix(10)))
78 .navigationBarTitleDisplayMode(.inline)
79 .task { await model.load() }
80 .refreshable { await model.load() }
81 }
82}