krz/hutch

an ios client for sourcehut

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

v2.7.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}