1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
|
import SwiftUI
struct ProjectDetailView: View {
let project: Project
@Environment(AppState.self) private var appState
@Environment(\.dismiss) private var dismiss
var body: some View {
List {
headerSection
repositoriesSection
trackersSection
mailingListsSection
}
.navigationTitle(project.name)
.navigationBarTitleDisplayMode(.inline)
}
@ViewBuilder
private var headerSection: some View {
Section {
VStack(alignment: .leading, spacing: 8) {
Text(project.name)
.font(.headline)
if let description = project.description, !description.isEmpty {
Text(description)
.font(.subheadline)
.foregroundStyle(.secondary)
}
if let website = project.website, let url = URL(string: website) {
Link(destination: url) {
Label(website, systemImage: "link")
.font(.subheadline)
}
}
}
.padding(.vertical, 4)
}
}
@ViewBuilder
private var repositoriesSection: some View {
if !project.sources.isEmpty {
Section("Repositories") {
ForEach(project.sources) { source in
Button {
Task {
try? await appState.openProjectSource(source)
dismiss()
}
} label: {
ProjectResourceRow(
title: source.name,
subtitle: source.owner.canonicalName,
detail: source.description
)
}
.buttonStyle(.plain)
}
}
}
}
@ViewBuilder
private var trackersSection: some View {
if !project.trackers.isEmpty {
Section("Trackers") {
ForEach(project.trackers) { tracker in
Button {
Task {
try? await appState.openProjectTracker(tracker)
dismiss()
}
} label: {
ProjectResourceRow(
title: tracker.name,
subtitle: tracker.owner.canonicalName,
detail: tracker.description
)
}
.buttonStyle(.plain)
}
}
}
}
@ViewBuilder
private var mailingListsSection: some View {
if !project.mailingLists.isEmpty {
Section("Mailing Lists") {
ForEach(project.mailingLists) { mailingList in
Button {
appState.openMailingList(mailingList.inboxReference)
dismiss()
} label: {
ProjectResourceRow(
title: mailingList.name,
subtitle: mailingList.owner.canonicalName,
detail: mailingList.description
)
}
.buttonStyle(.plain)
}
}
}
}
}
private struct ProjectResourceRow: View {
let title: String
let subtitle: String
let detail: String?
var body: some View {
VStack(alignment: .leading, spacing: 4) {
Text(title)
.font(.subheadline.weight(.medium))
Text(subtitle)
.font(.caption)
.foregroundStyle(.secondary)
.lineLimit(1)
if let detail, !detail.isEmpty {
Text(detail)
.font(.caption)
.foregroundStyle(.tertiary)
.lineLimit(2)
}
}
.padding(.vertical, 2)
}
}
|