49 lines
2 KiB
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
|
|
}
|
|
}
|