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:
@@ -88,6 +88,7 @@ struct ContentView: View {
|
||||
}
|
||||
.onChange(of: scenePhase) { phase in
|
||||
if phase == .active {
|
||||
taskVM.reload()
|
||||
NotificationManager.shared.reschedule(workoutVM: workoutVM, taskVM: taskVM)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user