a native ios client for gitbay

client ios swift

https://gitbay.org

gitbay/Views/Issues/MilestoneListView.swift

ui-smoke
gitbay-ios/gitbay/Views/Issues/MilestoneListView.swift history · blame · raw

74 lines · 2466 bytes

 1import SwiftUI
 2
 3/// Milestones and their progress.
 4struct MilestoneListView: View {
 5
 6    @State private var model: MilestoneListViewModel
 7
 8    init(client: GitbayClient, repo: String) {
 9        _model = State(initialValue: MilestoneListViewModel(client: client, repoPath: repo))
10    }
11
12    var body: some View {
13        List {
14            Picker("State", selection: Bindable(model).filter) {
15                ForEach(MilestoneListViewModel.StateFilter.allCases) { filter in
16                    Text(filter.rawValue.capitalized).tag(filter)
17                }
18            }
19            .pickerStyle(.segmented)
20            .listRowBackground(Color.clear)
21            .listRowInsets(EdgeInsets())
22
23            ForEach(model.state.value ?? []) { milestone in
24                MilestoneRow(milestone: milestone)
25            }
26        }
27        .overlay { LoadStateOverlay(state: model.state) }
28        .navigationTitle("Milestones")
29        .navigationBarTitleDisplayMode(.inline)
30        .task { await model.load() }
31        .refreshable { await model.load() }
32    }
33}
34
35private struct MilestoneRow: View {
36    let milestone: Milestone
37
38    private var total: Int { milestone.open + milestone.closed }
39    private var fraction: Double {
40        total == 0 ? 0 : Double(milestone.closed) / Double(total)
41    }
42
43    var body: some View {
44        VStack(alignment: .leading, spacing: 5) {
45            HStack(spacing: 6) {
46                Text(milestone.title)
47                    .font(.gbSans(.subheadline).weight(.medium))
48                if milestone.state != "open" {
49                    GBChip(milestone.state, .gbDone)
50                }
51                Spacer()
52                if let due = milestone.due, !due.isEmpty {
53                    Text(due)
54                        .font(.gbSans(.caption))
55                        .foregroundStyle(.tertiary)
56                }
57            }
58            if let description = milestone.description, !description.isEmpty {
59                Text(description)
60                    .font(.gbSans(.caption))
61                    .foregroundStyle(.secondary)
62                    .lineLimit(2)
63            }
64            if total > 0 {
65                ProgressView(value: fraction)
66                    .tint(.gbAccent)
67                Text("\(milestone.closed) of \(total) closed")
68                    .font(.gbSans(.caption2))
69                    .foregroundStyle(.secondary)
70            }
71        }
72        .padding(.vertical, 2)
73    }
74}