import SwiftUI /// One commit: what it says, who signed it, whether it passed, and what /// it changed. struct CommitView: View { @State private var model: CommitDetailViewModel init(client: GitbayClient, repo: String, sha: String) { _model = State(initialValue: CommitDetailViewModel( client: client, repoPath: repo, sha: sha )) } var body: some View { List { if let commit = model.state.value { Section { VStack(alignment: .leading, spacing: 6) { Text(commit.subject) .font(.gbSans(.headline)) if let message = commit.message, !message.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty { Text(message.trimmingCharacters(in: .whitespacesAndNewlines)) .font(.gbSans(.subheadline)) .foregroundStyle(.secondary) } HStack(spacing: 6) { Text(commit.shortSHA) .font(.gbMono(.caption)) .foregroundStyle(.secondary) SignatureBadge(signature: commit.signature) } HStack(spacing: 6) { Text(commit.authorName) Text(commit.date, format: .dateTime.year().month().day()) .foregroundStyle(.tertiary) } .font(.gbSans(.caption)) .foregroundStyle(.secondary) } .padding(.vertical, 2) } if let checks = commit.checks, !checks.isEmpty { Section("Checks") { ForEach(checks) { check in HStack { Image(systemName: check.state == "success" ? "checkmark.circle.fill" : check.state == "pending" ? "circle.dotted" : "xmark.circle.fill") .foregroundStyle(check.state == "success" ? Color.gbOK : check.state == "pending" ? Color.gbWarn : Color.gbBad) Text(check.context) .font(.gbSans(.subheadline)) Spacer() Text(check.state) .font(.gbSans(.caption)) .foregroundStyle(.secondary) } } } } if let diff = model.diff, !diff.files.isEmpty { Section("Changes +\(diff.additions) −\(diff.deletions)") { ForEach(diff.files) { file in NavigationLink(value: RepoRoute.file( repo: model.repoPath, path: file.displayPath, ref: commit.sha )) { HStack { Text(file.displayPath) .font(.gbMono(.caption)) .lineLimit(1) .truncationMode(.head) Spacer() Text("+\(file.additions)").foregroundStyle(Color.gbOK) Text("−\(file.deletions)").foregroundStyle(Color.gbBad) } .font(.gbSans(.caption2)) } } } } } } .overlay { LoadStateOverlay(state: model.state) } .navigationTitle(String(model.sha.prefix(10))) .navigationBarTitleDisplayMode(.inline) .toolbar { ToolbarItem(placement: .topBarTrailing) { if model.diff?.files.isEmpty == false { NavigationLink(value: RepoRoute.commitDiff( repo: model.repoPath, sha: model.sha )) { Image(systemName: "plus.forwardslash.minus") } .accessibilityIdentifier("commit-diff-button") } } } .task { await model.load() } .refreshable { await model.load() } } } /// The commit's patch, rendered by the same view the MR diff uses. struct CommitDiffView: View { @State private var model: CommitDetailViewModel init(client: GitbayClient, repo: String, sha: String) { _model = State(initialValue: CommitDetailViewModel( client: client, repoPath: repo, sha: sha )) } var body: some View { ZStack { Color.clear if let diff = model.diff, !diff.files.isEmpty { List { ForEach(diff.files) { file in DiffFileSection(file: file) } } .listStyle(.plain) } } .overlay { LoadStateOverlay(state: model.state) } .navigationTitle("Diff") .navigationBarTitleDisplayMode(.inline) .task { await model.load() } } }