import Foundation import Observation import UserNotifications /// Lokale tägliche Reminder. Reines `UNUserNotificationCenter` — /// keine Push-Backend-Anbindung, keine Crash-Reporter, kein SaaS /// (Compliance, siehe `mana/docs/COMPLIANCE.md`). @MainActor @Observable final class NotificationManager { enum AuthorizationStatus: Sendable { case unknown case authorized case denied } private(set) var authorization: AuthorizationStatus = .unknown private let identifier = "ev.mana.cards.dailyReminder" private let store = UserDefaults.standard /// Persistiert User-Pref. Format: ISO-Stunde:Minute (default 18:00). var reminderHour: Int { get { store.object(forKey: "reminderHour") as? Int ?? 18 } set { store.set(newValue, forKey: "reminderHour") } } var reminderMinute: Int { get { store.object(forKey: "reminderMinute") as? Int ?? 0 } set { store.set(newValue, forKey: "reminderMinute") } } var remindersEnabled: Bool { get { store.bool(forKey: "remindersEnabled") } set { store.set(newValue, forKey: "remindersEnabled") } } func refreshAuthorization() async { let settings = await UNUserNotificationCenter.current().notificationSettings() switch settings.authorizationStatus { case .authorized, .provisional, .ephemeral: authorization = .authorized case .denied: authorization = .denied default: authorization = .unknown } } /// Permission anfragen. Beim ersten Aufruf zeigt iOS den System-Prompt. func requestAuthorization() async -> Bool { let center = UNUserNotificationCenter.current() do { let granted = try await center.requestAuthorization(options: [.alert, .badge, .sound]) authorization = granted ? .authorized : .denied return granted } catch { authorization = .denied return false } } /// Tägliche Reminder neu planen. Bei `remindersEnabled = false` /// werden alle bestehenden Notifications gecancelt. func reschedule() async { let center = UNUserNotificationCenter.current() center.removePendingNotificationRequests(withIdentifiers: [identifier]) guard remindersEnabled, authorization == .authorized else { return } let content = UNMutableNotificationContent() content.title = "Cards" content.body = "Ein paar Karten warten auf dich." content.sound = .default var components = DateComponents() components.hour = reminderHour components.minute = reminderMinute let trigger = UNCalendarNotificationTrigger(dateMatching: components, repeats: true) let request = UNNotificationRequest(identifier: identifier, content: content, trigger: trigger) do { try await center.add(request) } catch { Log.app.error("Notification schedule failed: \(error.localizedDescription, privacy: .public)") } } }