import Foundation import UserNotifications class NotificationManager: NSObject, UNUserNotificationCenterDelegate { static let shared = NotificationManager() /// UNUserNotificationCenter requires a valid .app bundle. /// When running the bare binary (e.g. via build-test.sh) we skip notifications. private var isInsideBundle: Bool { return Bundle.main.bundleIdentifier != nil } private override init() { super.init() if isInsideBundle { UNUserNotificationCenter.current().delegate = self } } func requestPermission() { guard isInsideBundle else { NSLog("Pommedoro: skipping notification permission (not running from .app bundle)") return } UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .sound]) { granted, error in if let error = error { NSLog("Pommedoro: notification permission error: \(error.localizedDescription)") } } } func postCycleStarted(durationMinutes: Int) { guard isInsideBundle else { return } let content = UNMutableNotificationContent() content.title = "Pommedoro" content.body = "Cycle started — \(durationMinutes) minutes" content.sound = .default let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 1, repeats: false) let request = UNNotificationRequest( identifier: "pommedoro-cycle-start-\(UUID().uuidString)", content: content, trigger: trigger ) UNUserNotificationCenter.current().add(request) { error in if let error = error { NSLog("Pommedoro: failed to post notification: \(error.localizedDescription)") } } } func userNotificationCenter( _ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void ) { completionHandler([.banner, .sound]) } }