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/Widgets/EventLiveActivity.swift

6c4b28384d65e09486e0ad62f2ac9d10401cc80c
moonrise/App/Widgets/EventLiveActivity.swift history · blame · raw

86 lines · 3735 bytes

 1import ActivityKit
 2import SwiftUI
 3import WidgetKit
 4
 5struct EventLiveActivity: Widget {
 6    private let ink = Color(red: 0.95, green: 0.95, blue: 0.93)
 7
 8    var body: some WidgetConfiguration {
 9        ActivityConfiguration(for: EventActivityAttributes.self) { context in
10            EventActivityView(context: context)
11                .foregroundStyle(ink)
12        } dynamicIsland: { context in
13            DynamicIsland {
14                DynamicIslandExpandedRegion(.leading) {
15                    Text(context.attributes.title).font(.system(size: 14, weight: .medium))
16                }
17                DynamicIslandExpandedRegion(.trailing) {
18                    Text(context.attributes.launch, style: .timer).font(.system(size: 14).monospacedDigit())
19                }
20                DynamicIslandExpandedRegion(.bottom) {
21                    Text("\(context.state.phase) · \(Int(context.state.distanceKm).formatted()) km").font(.system(size: 12)).opacity(0.8)
22                }
23            } compactLeading: {
24                Image(systemName: "moon.stars")
25            } compactTrailing: {
26                IslandWidth {
27                    liveTimer(context.attributes.launch).font(.system(size: 12).monospacedDigit())
28                } limited: {
29                    // Mission time in days once past the first day; the full timer does not fit.
30                    if context.attributes.launch < .now.addingTimeInterval(-86_400) {
31                        Text("\(Int(-context.attributes.launch.timeIntervalSinceNow / 86_400)) d").font(.system(size: 12).monospacedDigit())
32                    } else {
33                        liveTimer(context.attributes.launch).font(.system(size: 12).monospacedDigit())
34                    }
35                }
36            } minimal: {
37                Image(systemName: "moon.stars")
38            }
39        }
40        .supplementalActivityFamilies([.small])
41    }
42}
43
44private struct EventActivityView: View {
45    @Environment(\.activityFamily) private var family
46    @Environment(\.isActivityFullscreen) private var fullscreen
47    var context: ActivityViewContext<EventActivityAttributes>
48
49    var body: some View {
50        Group {
51            if family == .small { small } else { medium }
52        }
53        // StandBy: black, to blend with the clock face.
54        .activityBackgroundTint(fullscreen ? .black : Color(red: 0.043, green: 0.078, blue: 0.141))
55    }
56
57    /// Apple Watch Smart Stack and CarPlay.
58    private var small: some View {
59        VStack(alignment: .leading, spacing: 2) {
60            Text(context.attributes.title).font(.system(size: 13, weight: .medium)).lineLimit(1)
61            liveTimer(context.attributes.launch).font(.system(size: 17, weight: .medium).monospacedDigit())
62        }
63        .frame(maxWidth: .infinity, alignment: .leading)
64        .padding(12)
65    }
66
67    private var medium: some View {
68        VStack(alignment: .leading, spacing: 4) {
69            HStack {
70                Text(context.attributes.title).font(.system(size: 17, weight: .medium))
71                Spacer()
72                Text(context.attributes.launch, style: .timer).font(.system(size: 17, weight: .medium).monospacedDigit())
73                    .multilineTextAlignment(.trailing)
74            }
75            Text(context.state.phase).font(.system(size: 14))
76            HStack {
77                Text("\(Int(context.state.distanceKm).formatted()) km from Earth").font(.system(size: 12)).opacity(0.8)
78                Spacer()
79                if let next = context.state.next, let at = context.state.nextAt {
80                    Text("\(next) \(at, style: .relative)").font(.system(size: 12)).opacity(0.8).lineLimit(1)
81                }
82            }
83        }
84        .padding(16)
85    }
86}