Task context menu (new shared TaskMenuItems) consistent across Today, Matrix, and Calendar: Pin · Date · Move · Priority · Tags · Add to Live Activity · Delete. Adds TaskViewModel.setPriority. Live Activity (ActivityKit): - TaskActivityAttributes shared between app and widget targets - LiveActivityManager (start/update/end/toggle, iOS 16.1+) - TaskLiveActivity widget: lock-screen banner + Dynamic Island - NSSupportsLiveActivities via project.yml; project regenerated Calendar events (non-subscription, writable only): - Edit via system EKEventEditViewController (EventEditView wrapper) - Delete via new CalendarStore.deleteEvent Also sweeps in prior in-progress edits already present in the working tree (AuthManager, NotificationManager, TaskItem, task views) and the updated ISSUES.md. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
117 lines
4.1 KiB
Swift
117 lines
4.1 KiB
Swift
import WidgetKit
|
||
import SwiftUI
|
||
import AppIntents
|
||
|
||
// MARK: - Shared Entry
|
||
|
||
struct KisaniEntry: TimelineEntry {
|
||
let date: Date
|
||
let task: WidgetTask?
|
||
let allTasks: [WidgetTask]
|
||
let streak: Int
|
||
}
|
||
|
||
// MARK: - Shared Provider
|
||
|
||
struct KisaniProvider: TimelineProvider {
|
||
func placeholder(in context: Context) -> KisaniEntry {
|
||
let sample = WidgetTask(id: UUID(), title: "Mums Birthday", dueDate: Date().addingTimeInterval(86400 * 60),
|
||
isComplete: false, quadrant: "Urgent + Important", category: "birthday")
|
||
return KisaniEntry(date: .now, task: sample, allTasks: [sample], streak: 7)
|
||
}
|
||
|
||
func getSnapshot(in context: Context, completion: @escaping (KisaniEntry) -> Void) {
|
||
let tasks = loadWidgetTasks()
|
||
completion(KisaniEntry(date: .now, task: nextDueTask(from: tasks), allTasks: tasks, streak: loadWorkoutStreak()))
|
||
}
|
||
|
||
func getTimeline(in context: Context, completion: @escaping (Timeline<KisaniEntry>) -> Void) {
|
||
let tasks = loadWidgetTasks()
|
||
let entry = KisaniEntry(date: .now, task: nextDueTask(from: tasks), allTasks: tasks, streak: loadWorkoutStreak())
|
||
// Refresh every 30 minutes or when app triggers WidgetCenter.reloadAllTimelines()
|
||
let next = Calendar.current.date(byAdding: .minute, value: 30, to: .now)!
|
||
completion(Timeline(entries: [entry], policy: .after(next)))
|
||
}
|
||
}
|
||
|
||
// MARK: - Day Progress Widget ("Day done %")
|
||
|
||
struct DayProgressWidget: Widget {
|
||
let kind = "KisaniDayProgress"
|
||
var body: some WidgetConfiguration {
|
||
StaticConfiguration(kind: kind, provider: KisaniProvider()) { _ in
|
||
ProgressDotView(period: .day, accent: Color(red: 0.48, green: 0.87, blue: 0.80))
|
||
}
|
||
.configurationDisplayName("Day Progress")
|
||
.description("How much of today has elapsed, as a dot grid.")
|
||
.supportedFamilies([.systemSmall, .systemMedium])
|
||
}
|
||
}
|
||
|
||
// MARK: - Tasks Done Today Widget ("Tasks complete %")
|
||
|
||
struct TasksCompleteWidget: Widget {
|
||
let kind = "KisaniTasksComplete"
|
||
var body: some WidgetConfiguration {
|
||
StaticConfiguration(kind: kind, provider: KisaniProvider()) { _ in
|
||
TasksCompleteView()
|
||
}
|
||
.configurationDisplayName("Tasks Done Today")
|
||
.description("Percentage of today's tasks you've completed.")
|
||
.supportedFamilies([.systemSmall, .systemMedium])
|
||
}
|
||
}
|
||
|
||
// MARK: - Lock Screen Widgets
|
||
|
||
struct LockScreenWidget: Widget {
|
||
let kind = "KisaniLockScreen"
|
||
|
||
var body: some WidgetConfiguration {
|
||
StaticConfiguration(kind: kind, provider: KisaniProvider()) { entry in
|
||
if let task = entry.task {
|
||
LockScreenRectView(task: task)
|
||
} else {
|
||
LockScreenRectView(task: WidgetTask(id: UUID(), title: "No tasks",
|
||
dueDate: nil, isComplete: false, quadrant: "Urgent + Important", category: "reminder"))
|
||
}
|
||
}
|
||
.configurationDisplayName("Task (Lock Screen)")
|
||
.description("Glass countdown on your lock screen.")
|
||
.supportedFamilies([.accessoryRectangular])
|
||
}
|
||
}
|
||
|
||
struct LockScreenCircularWidget: Widget {
|
||
let kind = "KisaniLockCircle"
|
||
|
||
var body: some WidgetConfiguration {
|
||
StaticConfiguration(kind: kind, provider: KisaniProvider()) { entry in
|
||
if let task = entry.task {
|
||
LockScreenCircularView(task: task)
|
||
} else {
|
||
LockScreenCircularView(task: WidgetTask(id: UUID(), title: "–",
|
||
dueDate: nil, isComplete: false, quadrant: "Urgent + Important", category: "reminder"))
|
||
}
|
||
}
|
||
.configurationDisplayName("Days Left (Lock Screen)")
|
||
.description("Circular countdown on your lock screen.")
|
||
.supportedFamilies([.accessoryCircular])
|
||
}
|
||
}
|
||
|
||
// MARK: - Widget Bundle
|
||
|
||
@main
|
||
struct KisaniWidgetBundle: WidgetBundle {
|
||
var body: some Widget {
|
||
EventCountdownWidget()
|
||
MyTasksWidget()
|
||
DayProgressWidget()
|
||
TasksCompleteWidget()
|
||
LockScreenWidget()
|
||
LockScreenCircularWidget()
|
||
TaskLiveActivity()
|
||
}
|
||
}
|