import Accessibility import Foundation import Observation /// The observable model that owns the lifecycle of the transient in-app notifications. /// /// Posting a notification appends it to ``notifications``, announces its message to assistive technologies — since the visual /// banners are transient — and schedules its automatic dismissal a few seconds later, which is the only way a notification is /// dismissed. @MainActor @Observable public final class Notifier { // MARK: Properties /// The notifications currently presented, in post order. public private(set) var notifications: [AppNotification] = [] /// The time a posted notification stays visible before its automatic dismissal. @ObservationIgnored private let delayDismissal: Duration // MARK: Initializers /// Creates a notifier. /// /// - Parameter delayDismissal: The time a posted notification stays visible before its automatic dismissal. /// Defaults to four seconds. public init( delayDismissal: Duration = .seconds(4) ) { self.delayDismissal = delayDismissal } // MARK: Methods /// Posts a notification, announcing its message to assistive technologies and scheduling its automatic dismissal. /// /// - Parameters: /// - kind: The kind of the notification. /// - message: The localized message of the notification. /// - symbol: The system symbol of the notification's icon. public func post( _ kind: AppNotification.Kind, message: String, symbol: String ) { let notification = AppNotification( kind: kind, message: message, symbol: symbol ) notifications.append(notification) AccessibilityNotification.Announcement(message).post() Task { try? await Task.sleep(for: delayDismissal) notifications.removeAll { $0.id == notification.id } } } }