Sources/OrgstarWidgets/UpNextWidget.swift
83 lines · 2823 bytes
1import OrgApp
2import SwiftUI
3import WidgetKit
4
5/// The agenda on the Lock Screen: the next entry (rectangular), the next entry or the count
6/// (inline), today's open entries (circular).
7struct UpNextWidget: Widget {
8 var body: some WidgetConfiguration {
9 StaticConfiguration(kind: "sh.krz.orgstar.up-next", provider: AgendaProvider()) { entry in
10 UpNextView(entry: entry)
11 }
12 .configurationDisplayName("Up Next")
13 .description("The next agenda entry, or how many are open today.")
14 .supportedFamilies([.accessoryRectangular, .accessoryInline, .accessoryCircular])
15 }
16}
17
18struct UpNextView: View {
19 let entry: AgendaEntry
20 @Environment(\.widgetFamily) private var family
21
22 private var next: AgendaSnapshot.Row? { entry.snapshot?.upcoming(at: entry.date).first }
23 private var open: Int { entry.snapshot?.openCount(at: entry.date) ?? 0 }
24
25 var body: some View {
26 content
27 .widgetURL((next?.link ?? .agenda).url)
28 .containerBackground(for: .widget) { Color.clear }
29 }
30
31 @ViewBuilder
32 private var content: some View {
33 switch family {
34 case .accessoryInline:
35 if let next {
36 Text("\(next.gutter) \(next.title)")
37 } else {
38 Text(open == 0 ? "Agenda clear" : "\(open) open today")
39 }
40 case .accessoryCircular:
41 ZStack {
42 AccessoryWidgetBackground()
43 VStack(spacing: -2) {
44 Text("\(open)").font(.title2.weight(.semibold)).widgetAccentable()
45 Text("open").font(.caption2)
46 }
47 }
48 .accessibilityLabel("\(open) open today")
49 default:
50 if let next {
51 VStack(alignment: .leading, spacing: 0) {
52 Text(next.gutter).font(.headline).widgetAccentable().lineLimit(1)
53 Text(next.title).font(.body).lineLimit(2)
54 }
55 .frame(maxWidth: .infinity, alignment: .leading)
56 } else {
57 VStack(alignment: .leading, spacing: 0) {
58 Text("Agenda").font(.headline).widgetAccentable()
59 Text(entry.snapshot == nil ? "Open Orgstar" : "Nothing left today").font(.body)
60 }
61 .frame(maxWidth: .infinity, alignment: .leading)
62 }
63 }
64 }
65}
66
67#Preview("Rectangular", as: .accessoryRectangular) {
68 UpNextWidget()
69} timeline: {
70 AgendaEntry(date: .now, snapshot: .sample(.now))
71}
72
73#Preview("Inline", as: .accessoryInline) {
74 UpNextWidget()
75} timeline: {
76 AgendaEntry(date: .now, snapshot: .sample(.now))
77}
78
79#Preview("Circular", as: .accessoryCircular) {
80 UpNextWidget()
81} timeline: {
82 AgendaEntry(date: .now, snapshot: .sample(.now))
83}