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
|
.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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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() {
|
||||||
@@ -149,10 +184,11 @@ final class NotificationManager: NSObject, ObservableObject, @unchecked Sendable
|
|||||||
guard fireDate > now else { continue }
|
guard fireDate > now else { continue }
|
||||||
|
|
||||||
let content = UNMutableNotificationContent()
|
let content = UNMutableNotificationContent()
|
||||||
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.userInfo = ["taskId": task.id.uuidString, "deeplink": "tasks"]
|
content.categoryIdentifier = Self.categoryId
|
||||||
|
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)
|
||||||
center.add(UNNotificationRequest(
|
center.add(UNNotificationRequest(
|
||||||
@@ -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
|
||||||
Task { @MainActor in
|
let deeplink = info["deeplink"] as? String
|
||||||
switch deeplink {
|
let taskId = info["taskId"] as? String
|
||||||
case "workout":
|
|
||||||
ShortcutHandler.shared.handle(QuickAction.workout.rawValue)
|
switch response.actionIdentifier {
|
||||||
default:
|
|
||||||
ShortcutHandler.shared.handle(QuickAction.today.rawValue)
|
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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
completionHandler()
|
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() {
|
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)
|
||||||
|
|||||||
Reference in New Issue
Block a user