// // AskNotifier.swift // AT-IRC // // Local notifications for owner questions. True APNs remote push needs a // server-side sender and provisioning; polling while the app runs plus a // local notification is the shipping scope. // import Foundation import OSLog import UserNotifications final class AskNotifier: NSObject, UNUserNotificationCenterDelegate { static let shared = AskNotifier() private var prepared = false private let log = Logger(subsystem: "com.fedfork.irc.AT-IRC", category: "notify") private override init() { super.init() UNUserNotificationCenter.current().delegate = self } /// Ask once for alert/sound permission; a denial only leaves the in-app /// badge and question list. Scripted UI tests set /// ATIRC_SKIP_NOTIFICATION_PROMPT=1 so the system alert does not keep the /// app from becoming active. func prepare() { guard !prepared else { return } prepared = true #if DEBUG let skip = ProcessInfo.processInfo.environment["ATIRC_SKIP_NOTIFICATION_PROMPT"] == "1" log.notice("prepare skip=\(skip, privacy: .public)") if skip { return } #endif Task { _ = try? await UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .sound, .badge]) } } func announce(_ ask: AskQuestion) { let content = UNMutableNotificationContent() content.title = "Question from \(ask.asker)" content.body = ask.question content.sound = .default content.userInfo = ["askID": ask.id] let request = UNNotificationRequest( identifier: "atirc.ask.\(ask.id)", content: content, trigger: nil ) UNUserNotificationCenter.current().add(request) } /// Drop the notification once the question has been answered or decided. func clear(_ askID: String) { let identifier = "atirc.ask.\(askID)" UNUserNotificationCenter.current().removeDeliveredNotifications(withIdentifiers: [identifier]) UNUserNotificationCenter.current().removePendingNotificationRequests(withIdentifiers: [identifier]) } nonisolated func userNotificationCenter( _ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void ) { completionHandler([.banner, .sound, .list]) } }