tv-anarchy/Sources/TVAnarchyCore/NotificationsService.swift

49 lines
2 KiB
Swift

import Foundation
import Observation
#if canImport(UserNotifications)
import UserNotifications
#endif
/// Surfaces download events two ways: a macOS Notification Center alert (works when
/// the app is backgrounded; one-time permission prompt) AND an in-app banner via the
/// observable `lastBanner` (always set, even when notifications are denied or
/// unavailable e.g. a non-bundled test run). Posting is a no-op when
/// `notifyDownloads` is off.
@Observable
@MainActor
public final class NotificationsService {
public static let shared = NotificationsService()
public init() {}
@ObservationIgnored private var authorized = false
@ObservationIgnored private var asked = false
/// Most recent in-app banner message (the UI shows + auto-clears it).
public private(set) var lastBanner: String?
public func clearBanner() { lastBanner = nil }
/// Ask once for Notification Center permission (the in-app banner needs none).
public func requestAuthorizationIfNeeded() {
#if canImport(UserNotifications)
guard !asked else { return }
asked = true
UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .sound]) { [weak self] ok, _ in
Task { @MainActor in self?.authorized = ok }
}
#endif
}
/// Post a notification (+ banner). Gated by the `notifyDownloads` setting.
public func post(title: String, body: String = "") {
guard SettingsStore.load().notifyDownloads else { return }
lastBanner = body.isEmpty ? title : "\(title)\(body)"
#if canImport(UserNotifications)
guard authorized else { return }
let content = UNMutableNotificationContent()
content.title = title
if !body.isEmpty { content.body = body }
content.sound = .default
UNUserNotificationCenter.current().add(
UNNotificationRequest(identifier: UUID().uuidString, content: content, trigger: nil))
#endif
}
}