krz/moonrise

Night-sky companion for iPhone, Apple Watch and Vision Pro: what's up tonight, the Moon, planets, launches and missions, and a Library of the solar system. SwiftUI app with a Cloudflare Workers backend. astronomy cloudflare-workers ios night-sky space swiftui visionos watchos

App/Sources/TimelineScreen.swift

04b2b1d914788057bcd20945b876847386eb8f65
moonrise/App/Sources/TimelineScreen.swift history · blame · raw

51 lines · 2048 bytes

2 symbols in this file
 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}