Add unified task menu, Live Activity, and calendar event actions
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>
This commit is contained in:
@@ -111,5 +111,6 @@ struct KisaniWidgetBundle: WidgetBundle {
|
||||
TasksCompleteWidget()
|
||||
LockScreenWidget()
|
||||
LockScreenCircularWidget()
|
||||
TaskLiveActivity()
|
||||
}
|
||||
}
|
||||
|
||||
74
KisaniCalWidgets/TaskLiveActivity.swift
Normal file
74
KisaniCalWidgets/TaskLiveActivity.swift
Normal file
@@ -0,0 +1,74 @@
|
||||
import ActivityKit
|
||||
import WidgetKit
|
||||
import SwiftUI
|
||||
|
||||
@available(iOS 16.1, *)
|
||||
struct TaskLiveActivity: Widget {
|
||||
private let accent = Color(red: 0.93, green: 0.42, blue: 0.20) // brand orange
|
||||
|
||||
var body: some WidgetConfiguration {
|
||||
ActivityConfiguration(for: TaskActivityAttributes.self) { context in
|
||||
// ── Lock screen / banner ──
|
||||
HStack(spacing: 12) {
|
||||
Image(systemName: context.state.isComplete ? "checkmark.circle.fill" : "circle")
|
||||
.font(.system(size: 22))
|
||||
.foregroundColor(context.state.isComplete ? .green : accent)
|
||||
VStack(alignment: .leading, spacing: 2) {
|
||||
Text(context.attributes.title)
|
||||
.font(.system(size: 15, weight: .semibold))
|
||||
.foregroundColor(.white)
|
||||
.lineLimit(1)
|
||||
.strikethrough(context.state.isComplete)
|
||||
if let due = context.state.dueDate {
|
||||
Text(due, style: context.state.isComplete ? .date : .relative)
|
||||
.font(.system(size: 12, design: .monospaced))
|
||||
.foregroundColor(.white.opacity(0.6))
|
||||
}
|
||||
}
|
||||
Spacer()
|
||||
if let due = context.state.dueDate, !context.state.isComplete {
|
||||
Text(due, style: .timer)
|
||||
.font(.system(size: 16, weight: .bold, design: .monospaced))
|
||||
.foregroundColor(accent)
|
||||
.frame(maxWidth: 70)
|
||||
}
|
||||
}
|
||||
.padding(.horizontal, 16)
|
||||
.padding(.vertical, 12)
|
||||
.activityBackgroundTint(Color.black.opacity(0.55))
|
||||
.activitySystemActionForegroundColor(.white)
|
||||
|
||||
} dynamicIsland: { context in
|
||||
DynamicIsland {
|
||||
DynamicIslandExpandedRegion(.leading) {
|
||||
Image(systemName: "checklist")
|
||||
.foregroundColor(accent)
|
||||
}
|
||||
DynamicIslandExpandedRegion(.trailing) {
|
||||
if let due = context.state.dueDate, !context.state.isComplete {
|
||||
Text(due, style: .timer)
|
||||
.font(.system(.body, design: .monospaced))
|
||||
.foregroundColor(accent)
|
||||
.frame(maxWidth: 64)
|
||||
}
|
||||
}
|
||||
DynamicIslandExpandedRegion(.center) {
|
||||
Text(context.attributes.title)
|
||||
.font(.system(size: 14, weight: .semibold))
|
||||
.lineLimit(1)
|
||||
}
|
||||
} compactLeading: {
|
||||
Image(systemName: "checklist").foregroundColor(accent)
|
||||
} compactTrailing: {
|
||||
if let due = context.state.dueDate, !context.state.isComplete {
|
||||
Text(due, style: .timer)
|
||||
.font(.system(.caption2, design: .monospaced))
|
||||
.foregroundColor(accent)
|
||||
.frame(maxWidth: 44)
|
||||
}
|
||||
} minimal: {
|
||||
Image(systemName: "checklist").foregroundColor(accent)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user