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