import SwiftUI struct IssueListView: View { @State private var model: IssueListViewModel @State private var createModel: IssueCreateViewModel @State private var composing = false @State private var draftTitle = "" @State private var draftBody = "" init(client: GitbayClient, repo: String) { _model = State(initialValue: IssueListViewModel(client: client, repoPath: repo)) _createModel = State(initialValue: IssueCreateViewModel(client: client, repoPath: repo)) } var body: some View { List { Picker("State", selection: Bindable(model).filter) { ForEach(IssueListViewModel.StateFilter.allCases) { filter in Text(filter.rawValue.capitalized).tag(filter) } } .pickerStyle(.segmented) .listRowBackground(Color.clear) .listRowInsets(EdgeInsets()) ForEach(model.state.value ?? []) { issue in NavigationLink(value: IssueRoute.issue(repo: model.repoPath, number: issue.number)) { IssueRow(issue: issue) } } PageFooter(list: model.list) } .overlay { LoadStateOverlay(state: model.state) } .navigationTitle("Issues") .navigationBarTitleDisplayMode(.inline) .toolbar { ToolbarItem(placement: .topBarTrailing) { Button { composing = true } label: { Image(systemName: "plus") } .accessibilityIdentifier("issue-create-button") } } .sheet(isPresented: $composing) { ComposeSheet( heading: "New Issue", submitLabel: "Create", working: createModel.working, errorMessage: createModel.errorMessage, title: $draftTitle, bodyText: $draftBody ) { Task { if await createModel.create(title: draftTitle, body: draftBody) != nil { draftTitle = "" draftBody = "" composing = false await model.load() } } } } .task { await model.load() } .refreshable { await model.load() } } } private struct IssueRow: View { let issue: Issue var body: some View { VStack(alignment: .leading, spacing: 4) { HStack(alignment: .firstTextBaseline, spacing: 6) { Text("#\(issue.number)") .font(.gbMono(.caption)) .foregroundStyle(.secondary) Text(issue.title) .font(.gbSans(.subheadline).weight(.medium)) .lineLimit(2) } HStack(spacing: 6) { Image(systemName: issue.isOpen ? "circle" : "checkmark.circle.fill") .font(.gbSans(.caption2)) .foregroundStyle(issue.isOpen ? Color.gbOK : Color.gbBad) ForEach(issue.labels ?? [], id: \.self) { label in GBChip(label, .secondary) } Spacer() if let assignees = issue.assignees, !assignees.isEmpty { Text(assignees.joined(separator: ", ")) .font(.gbSans(.caption)) .foregroundStyle(.secondary) .lineLimit(1) } Text(issue.createdAt, format: .relative(presentation: .named)) .font(.gbSans(.caption)) .foregroundStyle(.secondary) } } .padding(.vertical, 2) } } nonisolated enum IssueRoute: Hashable { case list(repo: String) case issue(repo: String, number: Int64) }