diff options
| author | Christian Cleberg <[email protected]> | 2026-03-19 15:18:38 -0500 |
|---|---|---|
| committer | Christian Cleberg <[email protected]> | 2026-03-19 15:18:38 -0500 |
| commit | 659c76df9ff3926cb68886c16167808c23bd8f75 (patch) | |
| tree | 572bef546fcca19ad37bc1aa7cfb153eb2bf8eb7 /Hutch/Views/Projects/ProjectDetailView.swift | |
| parent | ed48e72520c51e4635cd3840dbc569bdd74c950f (diff) | |
| parent | 6ba4e967d5dfb5d3c7bb97a0f2662f3180595563 (diff) | |
| download | hutch-2.tar.gz hutch-2.tar.bz2 hutch-2.zip | |
v2.0: Merge branch 'dev'v2
Diffstat (limited to 'Hutch/Views/Projects/ProjectDetailView.swift')
| -rw-r--r-- | Hutch/Views/Projects/ProjectDetailView.swift | 135 |
1 files changed, 135 insertions, 0 deletions
diff --git a/Hutch/Views/Projects/ProjectDetailView.swift b/Hutch/Views/Projects/ProjectDetailView.swift new file mode 100644 index 0000000..89601a9 --- /dev/null +++ b/Hutch/Views/Projects/ProjectDetailView.swift @@ -0,0 +1,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) + } +} |
