Archived
Three things, one of which is not a watch problem at all. parseDate already treated a date-only string as all-day, but isAllDay came from the JSON flag alone. An event sent as "2026-09-12" with no flag therefore landed at midnight with isAllDay false, and every surface that formats a time put "00:00" in front of it — the widgets as much as the watch. Now derived from both. The date complication showed the day number and nothing else, because .accessoryCircularCapacity renders only currentValueLabel and the month sat in the label closure, where it is never drawn. It now stacks weekday over day number, which is what makes it read as a date rather than a number. The ring only appears while an appointment is running, and then shows how far through it we are. An empty track around an idle day said nothing, so there is no Gauge at all in that case. All-day events do not count as running: one would hold the ring at an arbitrary fraction for twenty-four hours. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
157 lines
5.3 KiB
Swift
157 lines
5.3 KiB
Swift
import SwiftUI
|
|
import WidgetKit
|
|
import CalendarrCore
|
|
|
|
/// Attaches the curved bezel label that a face with a bezel slot — Infograph —
|
|
/// renders across the top, where the tick marks otherwise are. Faces without
|
|
/// that slot ignore it, so it costs nothing to always provide.
|
|
private extension View {
|
|
func eventBezelLabel(_ entry: WatchComplicationEntry) -> some View {
|
|
let next = WatchComplicationSupport.nextEvent(in: entry.snapshot, at: entry.date)
|
|
return widgetLabel {
|
|
Text(WatchComplicationSupport.eventLabel(next, at: entry.date, language: entry.language))
|
|
}
|
|
}
|
|
}
|
|
|
|
/// A draining ring plus the remaining time.
|
|
struct CountdownComplicationView: View {
|
|
let entry: WatchComplicationEntry
|
|
@Environment(\.widgetFamily) private var family
|
|
|
|
var body: some View {
|
|
let next = WatchComplicationSupport.nextEvent(in: entry.snapshot, at: entry.date)
|
|
switch family {
|
|
case .accessoryInline:
|
|
inline(next)
|
|
default:
|
|
circular(next)
|
|
}
|
|
}
|
|
|
|
@ViewBuilder
|
|
private func circular(_ next: SnapshotEvent?) -> some View {
|
|
if let next {
|
|
let target = WatchComplicationSupport.countdownTarget(next, at: entry.date)
|
|
Gauge(value: WatchComplicationSupport.gaugeFraction(to: target, at: entry.date)) {
|
|
Image(systemName: "calendar")
|
|
} currentValueLabel: {
|
|
Text(WatchComplicationSupport.compactCountdown(to: target, at: entry.date,
|
|
language: entry.language))
|
|
.font(.system(size: 13))
|
|
.lineLimit(1)
|
|
.minimumScaleFactor(0.6)
|
|
}
|
|
.gaugeStyle(.accessoryCircularCapacity)
|
|
.widgetAccentable()
|
|
.eventBezelLabel(entry)
|
|
} else {
|
|
ZStack {
|
|
AccessoryWidgetBackground()
|
|
Image(systemName: "calendar").font(.system(size: 18))
|
|
}
|
|
}
|
|
}
|
|
|
|
@ViewBuilder
|
|
private func inline(_ next: SnapshotEvent?) -> some View {
|
|
if let next {
|
|
Label {
|
|
Text(WatchComplicationSupport.countdownTarget(next, at: entry.date), style: .timer)
|
|
} icon: {
|
|
Image(systemName: "timer")
|
|
}
|
|
} else {
|
|
Label(WatchL10n.t("watch.no_events", entry.language), systemImage: "calendar")
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Weekday and day number, the way a date is actually read. The ring appears
|
|
/// only while an appointment is running, and then shows how far through it we
|
|
/// are — an empty track around an idle day is noise, so there is no Gauge at
|
|
/// all in that case.
|
|
struct DateComplicationView: View {
|
|
let entry: WatchComplicationEntry
|
|
@Environment(\.widgetFamily) private var family
|
|
|
|
private var day: Int {
|
|
Calendar(identifier: .gregorian).component(.day, from: entry.date)
|
|
}
|
|
|
|
private var weekday: String {
|
|
WatchComplicationSupport.weekdayAbbreviation(entry.date, language: entry.language)
|
|
}
|
|
|
|
var body: some View {
|
|
switch family {
|
|
case .accessoryInline:
|
|
Label("\(weekday) \(day)", systemImage: "calendar")
|
|
default:
|
|
circular
|
|
}
|
|
}
|
|
|
|
@ViewBuilder
|
|
private var circular: some View {
|
|
if let running = WatchComplicationSupport.runningEvent(in: entry.snapshot, at: entry.date) {
|
|
Gauge(value: WatchComplicationSupport.progress(of: running, at: entry.date)) {
|
|
Text(weekday)
|
|
} currentValueLabel: {
|
|
dateStack
|
|
}
|
|
.gaugeStyle(.accessoryCircularCapacity)
|
|
.eventBezelLabel(entry)
|
|
} else {
|
|
ZStack {
|
|
AccessoryWidgetBackground()
|
|
dateStack
|
|
}
|
|
.eventBezelLabel(entry)
|
|
}
|
|
}
|
|
|
|
private var dateStack: some View {
|
|
VStack(spacing: -2) {
|
|
Text(weekday)
|
|
.font(.system(size: 11, weight: .semibold))
|
|
.widgetAccentable()
|
|
Text("\(day)")
|
|
.font(.system(size: 23, weight: .semibold))
|
|
.lineLimit(1)
|
|
.minimumScaleFactor(0.7)
|
|
}
|
|
}
|
|
}
|
|
|
|
/// How many appointments today. Says "busy", not "what".
|
|
struct TodayCountComplicationView: View {
|
|
let entry: WatchComplicationEntry
|
|
@Environment(\.widgetFamily) private var family
|
|
|
|
private var count: Int {
|
|
WatchComplicationSupport.todayCount(in: entry.snapshot, at: entry.date)
|
|
}
|
|
|
|
var body: some View {
|
|
switch family {
|
|
case .accessoryInline:
|
|
Label("\(count) \(WatchL10n.t("watch.events_today", entry.language))",
|
|
systemImage: "calendar")
|
|
default:
|
|
Gauge(value: WatchComplicationSupport.todayRemainingFraction(in: entry.snapshot,
|
|
at: entry.date)) {
|
|
Image(systemName: "calendar")
|
|
} currentValueLabel: {
|
|
Text("\(count)")
|
|
.font(.system(size: 20, weight: .semibold))
|
|
.lineLimit(1)
|
|
.minimumScaleFactor(0.6)
|
|
}
|
|
.gaugeStyle(.accessoryCircularCapacity)
|
|
.widgetAccentable()
|
|
.eventBezelLabel(entry)
|
|
}
|
|
}
|
|
}
|