import SwiftUI struct LogView: View { @State private var model: LogViewModel init(client: GitbayClient, repo: String, ref: String?, path: String?) { _model = State(initialValue: LogViewModel( client: client, repoPath: repo, ref: ref, path: path )) } /// Name what this history is of: a file, a ref, or the repository. private var title: String { if let path = model.path { return String(path.split(separator: "/").last ?? "") } return model.ref.map { "History · \($0)" } ?? "History" } var body: some View { List { ForEach(model.state.value ?? []) { commit in NavigationLink(value: RepoRoute.commit(repo: model.repoPath, sha: commit.sha)) { CommitRow(commit: commit) } } } .overlay { LoadStateOverlay(state: model.state) } .navigationTitle(title) .navigationBarTitleDisplayMode(.inline) .task { await model.load() } .refreshable { await model.load() } } } struct CommitRow: View { let commit: Commit var body: some View { VStack(alignment: .leading, spacing: 4) { Text(commit.subject) .font(.gbSans(.subheadline).weight(.medium)) .lineLimit(2) HStack(spacing: 6) { Text(commit.shortSHA) .font(.gbMono(.caption)) .foregroundStyle(.secondary) SignatureBadge(signature: commit.signature) Spacer() Text(commit.authorName) .font(.gbSans(.caption)) .foregroundStyle(.secondary) .lineLimit(1) Text(commit.date, format: .relative(presentation: .named)) .font(.gbSans(.caption)) .foregroundStyle(.secondary) } } .padding(.vertical, 2) } } /// The server's signature verdict, worn as a small badge. The states come /// from the registry; the app only chooses glyph and colour. struct SignatureBadge: View { let signature: Commit.Signature var body: some View { if let (icon, color, text) = presentation { Label { Text(text) } icon: { Image(systemName: icon) } .font(.gbSans(.caption2)) .foregroundStyle(color) .labelStyle(.titleAndIcon) } } /// Unsigned commits are the norm — no badge at all. Everything else /// says what the server concluded. private var presentation: (String, Color, String)? { switch signature.state { case .unsigned: nil case .verified: ("checkmark.seal.fill", .gbOK, signature.signer ?? "verified") case .signedUnknownKey: ("questionmark.diamond", .secondary, "unknown key") case .signedEmailMismatch: ("exclamationmark.triangle", .gbWarn, "email mismatch") case .signedKeyExpired: ("clock.badge.exclamationmark", .gbWarn, "key expired") case .signedKeyRevoked: ("xmark.seal", .gbBad, "key revoked") case .badSignature: ("xmark.seal.fill", .gbBad, "bad signature") case .unrecognized(let state): ("questionmark.circle", .secondary, state) } } }