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 .onChange(of: scenePhase) { phase in
if phase == .active { if phase == .active {
taskVM.reload()
NotificationManager.shared.reschedule(workoutVM: workoutVM, taskVM: taskVM) 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 checkInPrefix = "kisani.workout.checkin"
private let tasksId = "kisani.tasks.evening" private let tasksId = "kisani.tasks.evening"
private static let categoryId = "TASK_REMINDER"
private static let actionMarkId = "MARK_COMPLETE"
override private init() { override private init() {
super.init() super.init()
center.delegate = self center.delegate = self
registerCategories()
refreshAuthStatus() 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 // MARK: - Permission
func requestPermission() { func requestPermission() {
@@ -152,6 +187,7 @@ final class NotificationManager: NSObject, ObservableObject, @unchecked Sendable
content.title = task.title content.title = task.title
content.body = self.notifBody(for: task) content.body = self.notifBody(for: task)
content.sound = .default content.sound = .default
content.categoryIdentifier = Self.categoryId
content.userInfo = ["taskId": task.id.uuidString, "deeplink": "tasks"] content.userInfo = ["taskId": task.id.uuidString, "deeplink": "tasks"]
let trigComps = cal.dateComponents([.year, .month, .day, .hour, .minute], from: fireDate) let trigComps = cal.dateComponents([.year, .month, .day, .hour, .minute], from: fireDate)
@@ -205,13 +241,24 @@ extension NotificationManager: UNUserNotificationCenterDelegate {
didReceive response: UNNotificationResponse, didReceive response: UNNotificationResponse,
withCompletionHandler completionHandler: @escaping () -> Void 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 Task { @MainActor in
switch deeplink { switch deeplink {
case "workout": case "workout": ShortcutHandler.shared.handle(QuickAction.workout.rawValue)
ShortcutHandler.shared.handle(QuickAction.workout.rawValue) default: ShortcutHandler.shared.handle(QuickAction.today.rawValue)
default: }
ShortcutHandler.shared.handle(QuickAction.today.rawValue)
} }
} }
completionHandler() 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() { private func save() {
if let data = try? JSONEncoder().encode(tasks) { if let data = try? JSONEncoder().encode(tasks) {
UserDefaults.standard.set(data, forKey: persistKey) UserDefaults.standard.set(data, forKey: persistKey)