Files
KisaniCal/KisaniCalWidgets/MyTasksWidget.swift

149 lines
5.2 KiB
Swift
Raw Normal View History

import WidgetKit
import SwiftUI
import AppIntents
// MARK: - Interactive intents
/// Tapping a checkbox toggles the task's completion directly in the App Group.
struct ToggleTaskIntent: AppIntent {
static var title: LocalizedStringResource = "Toggle Task Complete"
@Parameter(title: "Task ID")
var taskId: String
init() {}
init(taskId: String) { self.taskId = taskId }
func perform() async throws -> some IntentResult {
toggleWidgetTaskComplete(id: taskId)
return .result()
}
}
/// The "+" button opens the app and asks it to present the add-task sheet.
struct AddTaskFromWidgetIntent: AppIntent {
static var title: LocalizedStringResource = "Add Task"
static var openAppWhenRun = true
func perform() async throws -> some IntentResult {
UserDefaults.kisani.set(true, forKey: "kisani.widget.openAddTask")
return .result()
}
}
// MARK: - Entry + Provider
struct TasksEntry: TimelineEntry {
let date: Date
let tasks: [WidgetTask]
let openCount: Int
}
struct TasksProvider: TimelineProvider {
func placeholder(in context: Context) -> TasksEntry {
TasksEntry(date: .now, tasks: [
WidgetTask(id: UUID(), title: "Pray for Uganda", dueDate: .now, isComplete: false,
quadrant: "Urgent + Important", category: "reminder"),
WidgetTask(id: UUID(), title: "Masters of the Universe", dueDate: .now, isComplete: false,
quadrant: "Schedule It", category: "reminder")
], openCount: 2)
}
func getSnapshot(in context: Context, completion: @escaping (TasksEntry) -> Void) {
completion(loadEntry())
}
func getTimeline(in context: Context, completion: @escaping (Timeline<TasksEntry>) -> Void) {
let next = Calendar.current.startOfDay(for: Date().addingTimeInterval(86400))
completion(Timeline(entries: [loadEntry()], policy: .after(next)))
}
private func loadEntry() -> TasksEntry {
let tasks = todayWidgetTasks()
return TasksEntry(date: .now, tasks: tasks, openCount: tasks.filter { !$0.isComplete }.count)
}
}
// MARK: - Widget
struct MyTasksWidget: Widget {
let kind = "KisaniMyTasks"
var body: some WidgetConfiguration {
StaticConfiguration(kind: kind, provider: TasksProvider()) { entry in
MyTasksView(entry: entry)
}
.configurationDisplayName("My Tasks")
.description("Today's tasks — check them off right from the widget.")
.supportedFamilies([.systemSmall, .systemMedium, .systemLarge])
}
}
struct MyTasksView: View {
let entry: TasksEntry
@Environment(\.widgetFamily) var family
private var maxRows: Int {
switch family {
case .systemLarge: return 9
case .systemMedium: return 4
default: return 3
}
}
var body: some View {
VStack(alignment: .leading, spacing: 8) {
HStack(spacing: 6) {
Text("Today")
.font(.system(size: 18, weight: .bold))
.foregroundColor(Color(red: 0.42, green: 0.5, blue: 0.96))
Text("\(entry.openCount)")
.font(.system(size: 15, weight: .semibold))
.foregroundColor(Color(white: 0.5))
Spacer()
Button(intent: AddTaskFromWidgetIntent()) {
Image(systemName: "plus")
.font(.system(size: 16, weight: .semibold))
.foregroundColor(Color(red: 0.42, green: 0.5, blue: 0.96))
}
.buttonStyle(.plain)
}
if entry.tasks.isEmpty {
Spacer()
Text("All clear for today 🎉")
.font(.system(size: 13))
.foregroundColor(Color(white: 0.5))
.frame(maxWidth: .infinity, alignment: .center)
Spacer()
} else {
ForEach(entry.tasks.prefix(maxRows)) { task in
HStack(spacing: 10) {
Button(intent: ToggleTaskIntent(taskId: task.id.uuidString)) {
Image(systemName: task.isComplete ? "checkmark.circle.fill" : "circle")
.font(.system(size: 18))
.foregroundColor(task.isComplete ? task.accentColor : Color(white: 0.45))
}
.buttonStyle(.plain)
Text(task.title)
.font(.system(size: 14))
.foregroundColor(task.isComplete ? Color(white: 0.45) : .white)
.strikethrough(task.isComplete, color: Color(white: 0.45))
.lineLimit(1)
Spacer(minLength: 0)
}
}
if entry.tasks.count > maxRows {
Text("+\(entry.tasks.count - maxRows) more")
.font(.system(size: 11, weight: .medium))
.foregroundColor(Color(white: 0.4))
.padding(.leading, 28)
}
Spacer(minLength: 0)
}
}
.containerBackground(.black, for: .widget)
}
}