import SwiftUI /// Milestones and their progress. struct MilestoneListView: View { @State private var model: MilestoneListViewModel init(client: GitbayClient, repo: String) { _model = State(initialValue: MilestoneListViewModel(client: client, repoPath: repo)) } var body: some View { List { Picker("State", selection: Bindable(model).filter) { ForEach(MilestoneListViewModel.StateFilter.allCases) { filter in Text(filter.rawValue.capitalized).tag(filter) } } .pickerStyle(.segmented) .listRowBackground(Color.clear) .listRowInsets(EdgeInsets()) ForEach(model.state.value ?? []) { milestone in MilestoneRow(milestone: milestone) } } .overlay { LoadStateOverlay(state: model.state) } .navigationTitle("Milestones") .navigationBarTitleDisplayMode(.inline) .task { await model.load() } .refreshable { await model.load() } } } private struct MilestoneRow: View { let milestone: Milestone private var total: Int { milestone.open + milestone.closed } private var fraction: Double { total == 0 ? 0 : Double(milestone.closed) / Double(total) } var body: some View { VStack(alignment: .leading, spacing: 5) { HStack(spacing: 6) { Text(milestone.title) .font(.gbSans(.subheadline).weight(.medium)) if milestone.state != "open" { GBChip(milestone.state, .gbDone) } Spacer() if let due = milestone.due, !due.isEmpty { Text(due) .font(.gbSans(.caption)) .foregroundStyle(.tertiary) } } if let description = milestone.description, !description.isEmpty { Text(description) .font(.gbSans(.caption)) .foregroundStyle(.secondary) .lineLimit(2) } if total > 0 { ProgressView(value: fraction) .tint(.gbAccent) Text("\(milestone.closed) of \(total) closed") .font(.gbSans(.caption2)) .foregroundStyle(.secondary) } } .padding(.vertical, 2) } }