krz/hutch

an ios client for sourcehut

clone: git clone https://gitbay.org/krz/hutch.git

v3.9.0: Hutch/Views/Repositories/RepositoryRowView.swift · raw

  1import SwiftUI
  2
  3struct RepositoryRowView: View {
  4    @Environment(AppState.self) private var appState
  5    @Environment(\.openURL) private var openURL
  6
  7    let repository: RepositorySummary
  8    let buildStatus: RepositoryBuildStatus
  9
 10    var body: some View {
 11        VStack(alignment: .leading, spacing: 4) {
 12            HStack(alignment: .firstTextBaseline) {
 13                Text(repository.name)
 14                    .font(.headline)
 15
 16                Spacer()
 17
 18                RepositoryForgeBadge(service: repository.service)
 19
 20                VisibilityBadge(visibility: repository.visibility)
 21            }
 22
 23            Text(repository.owner.canonicalName)
 24                .font(.subheadline)
 25                .foregroundStyle(.secondary)
 26
 27            if let description = repository.description,
 28               !description.isEmpty {
 29                Text(description)
 30                    .font(.subheadline)
 31                    .foregroundStyle(.secondary)
 32                    .lineLimit(2)
 33            }
 34
 35            HStack(spacing: 12) {
 36                if let defaultBranchName = repository.defaultBranchName {
 37                    Label(defaultBranchName, systemImage: "arrow.triangle.branch")
 38                        .font(.caption)
 39                        .foregroundStyle(.secondary)
 40                }
 41
 42                Spacer()
 43
 44                HStack(spacing: 6) {
 45                    ZStack {
 46                        if buildStatus != .none {
 47                            RepositoryBuildStatusIndicator(status: buildStatus)
 48                        }
 49                    }
 50                    .frame(width: 8, height: 8)
 51                    .accessibilityHidden(buildStatus == .none)
 52
 53                    Text(repository.updated.relativeDescription)
 54                        .font(.caption)
 55                        .foregroundStyle(.tertiary)
 56                }
 57            }
 58        }
 59        .padding(.vertical, 2)
 60        .contextMenu {
 61            if let url = SRHTWebURL.repository(repository) {
 62                Button {
 63                    openURL(url)
 64                } label: {
 65                    Label("Open in Browser", systemImage: "safari")
 66                }
 67            }
 68
 69            Button {
 70                if let url = SRHTWebURL.repository(repository)?.absoluteString {
 71                    appState.copyToPasteboard(url, label: "repository URL")
 72                }
 73            } label: {
 74                Label("Copy URL", systemImage: "doc.on.doc")
 75            }
 76
 77            Button {
 78                if let url = SRHTWebURL.httpsCloneURL(repository) {
 79                    appState.copyToPasteboard(url, label: "HTTPS clone URL")
 80                }
 81            } label: {
 82                Label("Copy HTTPS URL", systemImage: "doc.on.doc")
 83            }
 84
 85            Button {
 86                appState.copyToPasteboard(SRHTWebURL.sshCloneURL(repository), label: "SSH clone URL")
 87            } label: {
 88                Label("Copy SSH URL", systemImage: "terminal")
 89            }
 90
 91            Button {
 92                appState.copyToPasteboard(repository.rid, label: "repository RID")
 93            } label: {
 94                Label("Copy RID", systemImage: "number")
 95            }
 96        }
 97    }
 98}
 99
100private struct RepositoryBuildStatusIndicator: View {
101    let status: RepositoryBuildStatus
102
103    var body: some View {
104        Circle()
105            .fill(color)
106            .frame(width: 8, height: 8)
107            .overlay {
108                Circle()
109                    .strokeBorder(.primary.opacity(0.08))
110            }
111            .accessibilityLabel(accessibilityLabel)
112    }
113
114    private var color: Color {
115        switch status {
116        case .success:
117            .green
118        case .failed:
119            .red
120        case .running:
121            .orange
122        case .none:
123            .clear
124        }
125    }
126
127    private var accessibilityLabel: String {
128        switch status {
129        case .success:
130            "Latest build succeeded"
131        case .failed:
132            "Latest build failed"
133        case .running:
134            "Latest build is running"
135        case .none:
136            "No recent builds"
137        }
138    }
139}
140
141// MARK: - VisibilityBadge
142
143struct VisibilityBadge: View {
144    let visibility: Visibility
145
146    var body: some View {
147        Text(label)
148            .font(.caption2.weight(.medium))
149            .padding(.horizontal, 6)
150            .padding(.vertical, 2)
151            .background(color.opacity(0.15), in: Capsule())
152            .foregroundStyle(color)
153    }
154
155    private var label: String {
156        switch visibility {
157        case .publicVisibility:   "PUBLIC"
158        case .unlisted: "UNLISTED"
159        case .privateVisibility:  "PRIVATE"
160        }
161    }
162
163    private var color: Color {
164        switch visibility {
165        case .publicVisibility:   .green
166        case .unlisted: .orange
167        case .privateVisibility:  .red
168        }
169    }
170}