krz/orgstar

A native macOS editor for org-mode files. editor org-mode swift

Sources/OrgstarWidgets/UpNextWidget.swift

b98a6509c5ae75e5172dd333d1b6105fd6ceee0e
orgstar/Sources/OrgstarWidgets/UpNextWidget.swift history · blame · raw

83 lines · 2823 bytes

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