Event widget: upcoming-queue auto-select + cleaner config & preview
- Add "Upcoming queue" toggle to the Event Countdown widget. When on, it auto-tracks the Nth nearest upcoming event (Nearest / 2nd … 5th, new AutoSelectRank enum). When off, track a picked or fully custom event. - parameterSummary now shows only the relevant fields per mode (toggle + auto-select, or event/name/date), so the date input only appears for custom events. - Replace the dense 380-day placeholder with a ~90-day window so the gallery preview shows a legible dot grid. (The interactive task-checkbox widget already exists as "My Tasks".) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -10,12 +10,32 @@ import AppIntents
|
||||
// persisted per-instance by WidgetKit, the widget is fully self-contained and does
|
||||
// not depend on App Group data sharing.
|
||||
|
||||
// Which upcoming event to auto-track when the queue is on.
|
||||
enum AutoSelectRank: Int, AppEnum {
|
||||
case first = 1, second, third, fourth, fifth
|
||||
|
||||
static var typeDisplayRepresentation: TypeDisplayRepresentation = "Auto Select"
|
||||
static var caseDisplayRepresentations: [AutoSelectRank: DisplayRepresentation] = [
|
||||
.first: "Nearest to finish",
|
||||
.second: "2nd nearest to finish",
|
||||
.third: "3rd nearest to finish",
|
||||
.fourth: "4th nearest to finish",
|
||||
.fifth: "5th nearest to finish",
|
||||
]
|
||||
}
|
||||
|
||||
struct EventConfigIntent: WidgetConfigurationIntent {
|
||||
static var title: LocalizedStringResource = "Event Countdown"
|
||||
static var description = IntentDescription("Count down to any upcoming event with a dot grid.")
|
||||
static var description = IntentDescription("Count down to an event with a dot grid.")
|
||||
|
||||
// When on, the widget auto-tracks one of your upcoming events. When off, you
|
||||
// track a specific event you pick, or a fully custom name + date.
|
||||
@Parameter(title: "Upcoming queue", default: true)
|
||||
var upcomingQueue: Bool
|
||||
|
||||
@Parameter(title: "Auto select", default: .first)
|
||||
var autoSelect: AutoSelectRank
|
||||
|
||||
// Pick one of your existing events (tasks with a due date). When set, it
|
||||
// overrides the manual name/date fields below.
|
||||
@Parameter(title: "Track event")
|
||||
var event: EventEntity?
|
||||
|
||||
@@ -27,6 +47,25 @@ struct EventConfigIntent: WidgetConfigurationIntent {
|
||||
|
||||
@Parameter(title: "Counting from", default: Date.now)
|
||||
var startDate: Date
|
||||
|
||||
// Show only the relevant fields depending on the toggle — like a clean,
|
||||
// single-purpose config screen instead of every field at once.
|
||||
static var parameterSummary: some ParameterSummary {
|
||||
When(\.$upcomingQueue, .equalTo, true) {
|
||||
Summary("Track upcoming event") {
|
||||
\.$upcomingQueue
|
||||
\.$autoSelect
|
||||
}
|
||||
} otherwise: {
|
||||
Summary("Track a custom event") {
|
||||
\.$upcomingQueue
|
||||
\.$event
|
||||
\.$eventName
|
||||
\.$targetDate
|
||||
\.$startDate
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Event picker (reads your tasks from the App Group)
|
||||
@@ -141,10 +180,11 @@ struct EventEntry: TimelineEntry {
|
||||
|
||||
struct EventProvider: AppIntentTimelineProvider {
|
||||
func placeholder(in context: Context) -> EventEntry {
|
||||
// ~90-day window, ~60% elapsed → a clear, legible dot grid in the gallery.
|
||||
EventEntry(date: .now,
|
||||
eventName: "Mum's Birthday",
|
||||
start: Date().addingTimeInterval(-86400 * 320),
|
||||
target: Date().addingTimeInterval(86400 * 60))
|
||||
eventName: "IELTS",
|
||||
start: Date().addingTimeInterval(-86400 * 54),
|
||||
target: Date().addingTimeInterval(86400 * 36))
|
||||
}
|
||||
|
||||
func snapshot(for configuration: EventConfigIntent, in context: Context) async -> EventEntry {
|
||||
@@ -158,19 +198,43 @@ struct EventProvider: AppIntentTimelineProvider {
|
||||
return Timeline(entries: [entry], policy: .after(nextMidnight))
|
||||
}
|
||||
|
||||
/// Upcoming events (incomplete tasks with a future due date), nearest first.
|
||||
private func upcomingEvents() -> [EventEntity] {
|
||||
loadWidgetTasks()
|
||||
.filter { !$0.isComplete && ($0.dueDate ?? .distantPast) > Date() }
|
||||
.sorted { ($0.dueDate ?? .distantFuture) < ($1.dueDate ?? .distantFuture) }
|
||||
.map { EventEntity(id: $0.id.uuidString, title: $0.title, dueDate: $0.dueDate) }
|
||||
}
|
||||
|
||||
private func entry(for config: EventConfigIntent) -> EventEntry {
|
||||
let name: String
|
||||
let target: Date
|
||||
if let ev = config.event, let due = ev.dueDate {
|
||||
// A picked event supplies its own name + date.
|
||||
var name: String
|
||||
var target: Date
|
||||
|
||||
if config.upcomingQueue {
|
||||
// Auto-track the Nth nearest upcoming event.
|
||||
let upcoming = upcomingEvents()
|
||||
let idx = max(0, config.autoSelect.rawValue - 1)
|
||||
if idx < upcoming.count, let due = upcoming[idx].dueDate {
|
||||
name = upcoming[idx].title
|
||||
target = due
|
||||
} else {
|
||||
name = "No upcoming event"
|
||||
target = Date().addingTimeInterval(86400)
|
||||
}
|
||||
} else if let ev = config.event, let due = ev.dueDate {
|
||||
// A specifically picked event supplies its own name + date.
|
||||
name = ev.title
|
||||
target = due
|
||||
} else {
|
||||
// Fully custom event.
|
||||
name = config.eventName.isEmpty ? "My Event" : config.eventName
|
||||
target = config.targetDate
|
||||
}
|
||||
// Never let the start sit after the target.
|
||||
let start = min(config.startDate, target)
|
||||
|
||||
// Counting anchor: custom mode honors the picker; auto mode counts from
|
||||
// "now" so the grid fills toward the event. Never start after the target.
|
||||
let anchor = config.upcomingQueue ? Date() : config.startDate
|
||||
let start = min(anchor, target)
|
||||
let mode = UserDefaults.kisani.integer(forKey: countdownUnitKey)
|
||||
return EventEntry(date: .now, eventName: name, start: start, target: target, unitMode: mode)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user