Fix: tapping task notification now marks it complete

Tap body → marks complete + navigates to tasks.
Swipe action 'Mark Complete' → marks complete without opening app.
TaskViewModel.reload() called on foreground to sync UserDefaults changes
made while app was backgrounded.

Co-Authored-By: Kutesir <tqwyy79vzn@privaterelay.appleid.com>
This commit is contained in:
Robin Kutesa
2026-06-01 03:52:10 +03:00
parent 12e3cfdd13
commit 0c48ca888b
3 changed files with 66 additions and 11 deletions

View File

@@ -88,6 +88,7 @@ struct ContentView: View {
}
.onChange(of: scenePhase) { phase in
if phase == .active {
taskVM.reload()
NotificationManager.shared.reschedule(workoutVM: workoutVM, taskVM: taskVM)
}
}

View File

@@ -11,12 +11,47 @@ final class NotificationManager: NSObject, ObservableObject, @unchecked Sendable
private let checkInPrefix = "kisani.workout.checkin"
private let tasksId = "kisani.tasks.evening"
private static let categoryId = "TASK_REMINDER"
private static let actionMarkId = "MARK_COMPLETE"
override private init() {
super.init()
center.delegate = self
registerCategories()
refreshAuthStatus()
}
private func registerCategories() {
let markAction = UNNotificationAction(
identifier: Self.actionMarkId,
title: "Mark Complete",
options: []
)
let category = UNNotificationCategory(
identifier: Self.categoryId,
actions: [markAction],
intentIdentifiers: [],
options: .customDismissAction
)
center.setNotificationCategories([category])
}
// MARK: - Complete task directly in UserDefaults (called from background)
private func markTaskComplete(taskId: String) {
let uid = AuthManager.shared.currentUser?.id ?? "shared"
let key = "kisani.tasks.v2.\(uid)"
guard let data = UserDefaults.standard.data(forKey: key),
var tasks = try? JSONDecoder().decode([TaskItem].self, from: data),
let uuid = UUID(uuidString: taskId),
let idx = tasks.firstIndex(where: { $0.id == uuid })
else { return }
tasks[idx].isComplete = true
tasks[idx].completedAt = Date()
if let encoded = try? JSONEncoder().encode(tasks) {
UserDefaults.standard.set(encoded, forKey: key)
}
}
// MARK: - Permission
func requestPermission() {
@@ -152,6 +187,7 @@ final class NotificationManager: NSObject, ObservableObject, @unchecked Sendable
content.title = task.title
content.body = self.notifBody(for: task)
content.sound = .default
content.categoryIdentifier = Self.categoryId
content.userInfo = ["taskId": task.id.uuidString, "deeplink": "tasks"]
let trigComps = cal.dateComponents([.year, .month, .day, .hour, .minute], from: fireDate)
@@ -205,13 +241,24 @@ extension NotificationManager: UNUserNotificationCenterDelegate {
didReceive response: UNNotificationResponse,
withCompletionHandler completionHandler: @escaping () -> Void
) {
let deeplink = response.notification.request.content.userInfo["deeplink"] as? String
let info = response.notification.request.content.userInfo
let deeplink = info["deeplink"] as? String
let taskId = info["taskId"] as? String
switch response.actionIdentifier {
case Self.actionMarkId:
// Swiped action: mark complete, don't open app
if let taskId { markTaskComplete(taskId: taskId) }
default:
// Tapped notification body
if let taskId { markTaskComplete(taskId: taskId) }
Task { @MainActor in
switch deeplink {
case "workout":
ShortcutHandler.shared.handle(QuickAction.workout.rawValue)
default:
ShortcutHandler.shared.handle(QuickAction.today.rawValue)
case "workout": ShortcutHandler.shared.handle(QuickAction.workout.rawValue)
default: ShortcutHandler.shared.handle(QuickAction.today.rawValue)
}
}
}
completionHandler()

View File

@@ -72,6 +72,13 @@ class TaskViewModel: ObservableObject {
}
}
func reload() {
guard let data = UserDefaults.standard.data(forKey: persistKey),
let saved = try? JSONDecoder().decode([TaskItem].self, from: data)
else { return }
tasks = saved
}
private func save() {
if let data = try? JSONEncoder().encode(tasks) {
UserDefaults.standard.set(data, forKey: persistKey)