gitbay/Views/Builds/BuildListView.swift

1c5622210d0f72efd86582ce195a6741401e7939
gitbay-ios/gitbay/Views/Builds/BuildListView.swift history · blame · raw

111 lines · 3638 bytes

  1import SwiftUI
  2
  3struct BuildListView: View {
  4
  5    @State private var model: BuildListViewModel
  6    @State private var triggering = false
  7    @State private var jobName = ""
  8
  9    init(client: GitbayClient, repo: String) {
 10        _model = State(initialValue: BuildListViewModel(client: client, repoPath: repo))
 11    }
 12
 13    var body: some View {
 14        List {
 15            if let error = model.actionError {
 16                Section {
 17                    GBNotice(error, .gbWarn)
 18                }
 19            }
 20            ForEach(model.state.value ?? []) { build in
 21                NavigationLink(value: BuildRoute.log(repo: model.repoPath, number: build.number)) {
 22                    BuildRow(build: build)
 23                }
 24            }
 25        }
 26        .overlay { LoadStateOverlay(state: model.state) }
 27        .navigationTitle("Builds")
 28        .navigationBarTitleDisplayMode(.inline)
 29        .toolbar {
 30            ToolbarItem(placement: .topBarTrailing) {
 31                Button {
 32                    jobName = model.latestJob ?? ""
 33                    triggering = true
 34                } label: {
 35                    Image(systemName: "play.circle")
 36                }
 37                .disabled(model.working)
 38                .accessibilityIdentifier("build-trigger-button")
 39            }
 40        }
 41        .alert("Trigger a build", isPresented: $triggering) {
 42            TextField("Job name", text: $jobName)
 43                .autocorrectionDisabled()
 44                .textInputAutocapitalization(.never)
 45            Button("Trigger") {
 46                Task { await model.trigger(job: jobName.trimmingCharacters(in: .whitespaces)) }
 47            }
 48            Button("Cancel", role: .cancel) {}
 49        } message: {
 50            Text("Runs the named job from .gitbay/ at the default branch head.")
 51        }
 52        .task { await model.load() }
 53        .refreshable { await model.load() }
 54    }
 55}
 56
 57private struct BuildRow: View {
 58    let build: Build
 59
 60    var body: some View {
 61        HStack(spacing: 10) {
 62            Image(systemName: icon)
 63                .foregroundStyle(color)
 64            VStack(alignment: .leading, spacing: 2) {
 65                Text("#\(build.number) \(build.job)")
 66                    .font(.gbSans(.subheadline).weight(.medium))
 67                HStack(spacing: 6) {
 68                    Text(build.ref)
 69                    Text(build.shortSHA)
 70                        .font(.gbMono(.caption))
 71                }
 72                .font(.gbSans(.caption))
 73                .foregroundStyle(.secondary)
 74            }
 75            Spacer()
 76            VStack(alignment: .trailing, spacing: 2) {
 77                Text(build.status)
 78                    .font(.gbSans(.caption).weight(.medium))
 79                    .foregroundStyle(color)
 80                Text(build.createdAt, format: .relative(presentation: .named))
 81                    .font(.gbSans(.caption))
 82                    .foregroundStyle(.secondary)
 83            }
 84        }
 85        .padding(.vertical, 2)
 86    }
 87
 88    private var icon: String {
 89        switch build.status {
 90        case "success": "checkmark.circle.fill"
 91        case "failure", "error": "xmark.circle.fill"
 92        case "running": "circle.dotted"
 93        case "queued", "pending": "clock"
 94        default: "questionmark.circle"
 95        }
 96    }
 97
 98    private var color: Color {
 99        switch build.status {
100        case "success": .gbOK
101        case "failure", "error": .gbBad
102        case "running", "queued", "pending": .gbWarn
103        default: .secondary
104        }
105    }
106}
107
108nonisolated enum BuildRoute: Hashable {
109    case list(repo: String)
110    case log(repo: String, number: Int64)
111}