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