App/Widgets/EventLiveActivity.swift
86 lines · 3735 bytes
2 symbols in this file
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}