App/Sources/TimelineScreen.swift
51 lines · 2048 bytes
1import Library
2import SwiftUI
3
4/// Missions in the Library by launch year, from Sputnik to today.
5struct ExplorationTimeline: View {
6 @State private var selected: LibraryPage?
7 private let ink = Color(red: 0.95, green: 0.95, blue: 0.93)
8 private let accent = Color(red: 0.88, green: 0.24, blue: 0.19)
9
10 private var decades: [(Int, [LibraryPage])] {
11 let missions = Library.pages(in: .missions).filter { $0.drawing?.launch != nil }.sorted { $0.drawing!.launch! < $1.drawing!.launch! }
12 let grouped = Dictionary(grouping: missions) { (Int($0.drawing!.launch!.prefix(4)) ?? 0) / 10 * 10 }
13 return grouped.keys.sorted().map { ($0, grouped[$0]!) }
14 }
15
16 var body: some View {
17 List {
18 ForEach(decades, id: \.0) { decade, missions in
19 Section("\(String(decade))s") {
20 ForEach(missions) { m in
21 Button { selected = m } label: { row(m) }
22 .buttonStyle(.plain)
23 .listRowBackground(Color.black)
24 }
25 }
26 }
27 }
28 .listStyle(.plain)
29 .scrollContentBackground(.hidden)
30 .background(Color.black.ignoresSafeArea())
31 .navigationTitle("Timeline")
32 .foregroundStyle(ink)
33 .sheet(item: $selected) { page in NavigationStack { LibraryPageView(page: page) } }
34 }
35
36 private func row(_ m: LibraryPage) -> some View {
37 let year = String(m.drawing?.launch?.prefix(4) ?? "")
38 let active = m.drawing?.status == "active"
39 return HStack(alignment: .firstTextBaseline, spacing: 12) {
40 Text(year).font(.footnote.monospacedDigit()).dimmed(0.7)
41 VStack(alignment: .leading, spacing: 2) {
42 Text(m.title).font(.body)
43 Text(m.subtitle).font(.footnote).dimmed(0.7)
44 }
45 Spacer()
46 if active {
47 Circle().fill(accent).frame(width: 7, height: 7).accessibilityLabel("Active")
48 }
49 }
50 }
51}