Add task persistence and per-task reminder notifications
Tasks now survive app restarts via UserDefaults JSON. Each task with a due time fires a notification at that exact time; date-only tasks fire at 9am on the due date. Completing or deleting a task cancels its pending notification. ContentView now reschedules on any task change.
This commit is contained in:
@@ -1,10 +1,11 @@
|
||||
import SwiftUI
|
||||
|
||||
// MARK: - Task Model
|
||||
struct TaskItem: Identifiable {
|
||||
struct TaskItem: Identifiable, Codable, Equatable {
|
||||
var id = UUID()
|
||||
var title: String
|
||||
var dueDate: Date?
|
||||
var hasTime: Bool = false
|
||||
var isComplete: Bool = false
|
||||
var completedAt: Date? = nil
|
||||
var quadrant: Quadrant = .urgent
|
||||
@@ -13,7 +14,7 @@ struct TaskItem: Identifiable {
|
||||
var isRecurring: Bool = false
|
||||
}
|
||||
|
||||
enum Quadrant: String, CaseIterable, Identifiable {
|
||||
enum Quadrant: String, CaseIterable, Identifiable, Codable {
|
||||
var id: String { rawValue }
|
||||
case urgent = "Urgent + Important"
|
||||
case schedule = "Schedule It"
|
||||
@@ -21,7 +22,7 @@ enum Quadrant: String, CaseIterable, Identifiable {
|
||||
case eliminate = "Eliminate"
|
||||
}
|
||||
|
||||
enum TaskCategory: String {
|
||||
enum TaskCategory: String, Codable {
|
||||
case reminder = "Reminder"
|
||||
case birthday = "Birthday"
|
||||
case domain = "Domain"
|
||||
@@ -29,7 +30,7 @@ enum TaskCategory: String {
|
||||
case custom = "Custom"
|
||||
}
|
||||
|
||||
enum TaskColor {
|
||||
enum TaskColor: String, Codable {
|
||||
case orange, green, blue, yellow
|
||||
|
||||
func color() -> Color {
|
||||
@@ -52,7 +53,24 @@ enum TaskColor {
|
||||
|
||||
// MARK: - Task View Model
|
||||
class TaskViewModel: ObservableObject {
|
||||
@Published var tasks: [TaskItem] = sampleTasks
|
||||
@Published var tasks: [TaskItem]
|
||||
|
||||
private static let persistKey = "kisani.tasks.v2"
|
||||
|
||||
init() {
|
||||
if let data = UserDefaults.standard.data(forKey: Self.persistKey),
|
||||
let saved = try? JSONDecoder().decode([TaskItem].self, from: data), !saved.isEmpty {
|
||||
tasks = saved
|
||||
} else {
|
||||
tasks = sampleTasks
|
||||
}
|
||||
}
|
||||
|
||||
private func save() {
|
||||
if let data = try? JSONEncoder().encode(tasks) {
|
||||
UserDefaults.standard.set(data, forKey: Self.persistKey)
|
||||
}
|
||||
}
|
||||
|
||||
var overdueTasks: [TaskItem] {
|
||||
let startOfDay = Calendar.current.startOfDay(for: Date())
|
||||
@@ -83,22 +101,26 @@ class TaskViewModel: ObservableObject {
|
||||
guard let idx = tasks.firstIndex(where: { $0.id == task.id }) else { return }
|
||||
tasks[idx].isComplete.toggle()
|
||||
tasks[idx].completedAt = tasks[idx].isComplete ? Date() : nil
|
||||
save()
|
||||
}
|
||||
func tasks(for quadrant: Quadrant) -> [TaskItem] {
|
||||
tasks.filter { $0.quadrant == quadrant }
|
||||
}
|
||||
|
||||
func addTask(title: String, dueDate: Date?, quadrant: Quadrant = .urgent,
|
||||
category: TaskCategory = .reminder, taskColor: TaskColor = .orange,
|
||||
isRecurring: Bool = false) {
|
||||
tasks.append(TaskItem(title: title, dueDate: dueDate, quadrant: quadrant,
|
||||
category: category, taskColor: taskColor, isRecurring: isRecurring))
|
||||
func addTask(title: String, dueDate: Date?, hasTime: Bool = false,
|
||||
quadrant: Quadrant = .urgent, category: TaskCategory = .reminder,
|
||||
taskColor: TaskColor = .orange, isRecurring: Bool = false) {
|
||||
tasks.append(TaskItem(title: title, dueDate: dueDate, hasTime: hasTime,
|
||||
quadrant: quadrant, category: category,
|
||||
taskColor: taskColor, isRecurring: isRecurring))
|
||||
save()
|
||||
}
|
||||
|
||||
func postpone(_ task: TaskItem, days: Int = 1) {
|
||||
guard let idx = tasks.firstIndex(where: { $0.id == task.id }) else { return }
|
||||
let base = tasks[idx].dueDate ?? Date()
|
||||
tasks[idx].dueDate = Calendar.current.date(byAdding: .day, value: days, to: base)
|
||||
save()
|
||||
}
|
||||
|
||||
func postponeAllOverdue(days: Int = 1) {
|
||||
@@ -108,10 +130,12 @@ class TaskViewModel: ObservableObject {
|
||||
let base = tasks[idx].dueDate ?? Date()
|
||||
tasks[idx].dueDate = Calendar.current.date(byAdding: .day, value: days, to: base)
|
||||
}
|
||||
save()
|
||||
}
|
||||
|
||||
func delete(_ task: TaskItem) {
|
||||
tasks.removeAll { $0.id == task.id }
|
||||
save()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user