import ComposableArchitecture2 import Foundation import Tagged /// A source whose content can change independently of notification delivery. /// /// The notification package treats both the identifier and version as opaque values. A consuming /// application decides what the source represents, how versions compare, and what should reload. public struct NotificationSource: Equatable, Hashable, Sendable { public enum IDTag {} public enum VersionTag {} public typealias ID = Tagged public typealias Version = Tagged public var id: ID public var version: Version public init(id: ID, version: Version) { self.id = id self.version = version } } /// An application-owned route with a stable provider-compatible string representation. public protocol NotificationRoute: Equatable, Sendable { init?(notificationRoute: String) var notificationRoute: String { get } } /// Provider-independent text displayed when a notification is visible to the user. public struct NotificationPresentation: Equatable, Sendable { /// How urgently providers and operating systems should present visible content. public enum Delivery: Equatable, Sendable { case standard /// A time-bounded alert that may use platform emergency-notification affordances. case emergency(expiresAt: Date) } public var body: String public var delivery: Delivery public var title: String public init( title: String, body: String, delivery: Delivery = .standard ) { self.body = body self.delivery = delivery self.title = title } } /// Source-backed application content addressed by an application-owned route. public struct NotificationContent: Equatable, Sendable { public var presentation: NotificationPresentation? public var route: Route public var source: NotificationSource public init( source: NotificationSource, route: Route, presentation: NotificationPresentation? = nil ) { self.presentation = presentation self.route = route self.source = source } } /// A provider-independent notification message decoded for one application route type. public enum NotificationMessage: Equatable, Sendable { case content(NotificationContent) case sourceAvailable(NotificationSource) } /// A request for notification signals about one source. public struct NotificationSubscription: Equatable, Hashable, Sendable { public var sourceID: NotificationSource.ID public init(sourceID: NotificationSource.ID) { self.sourceID = sourceID } } /// A transport-independent signal delivered to the application feature tree. public enum NotificationSignal: Equatable, Hashable, Sendable { case sourceAvailable(NotificationSource) } /// Collects the notification subscriptions published by mounted descendant features. public enum NotificationSubscriptionsPreferenceKey: FeaturePreferenceKey { public static let defaultValue: Set = [] public static func reduce( value: inout Set, nextValue: () -> Set ) { value.formUnion(nextValue()) } } extension FeatureProtocol { /// Publishes the sources for which this feature wants notification signals. public func notificationSubscriptions( _ subscriptions: Set ) -> some Feature { self.preference( key: NotificationSubscriptionsPreferenceKey.self, value: subscriptions ) } /// Publishes sources derived from the feature's mounted state. /// /// Use this overload when the desired subscriptions belong to feature state. The modifier's /// feature store is installed before its body reads the key path, so callers do not need to /// capture their feature store while composing `body`. public func notificationSubscriptions( _ subscriptions: KeyPath> ) -> some Feature { self.modifier( NotificationSubscriptionsModifier( subscriptions: subscriptions ) ) } } private struct NotificationSubscriptionsModifier: FeatureModifier { let subscriptions: KeyPath> let store = FeatureStore() func body(content: Content) -> some FeatureProtocol { content.preference( key: NotificationSubscriptionsPreferenceKey.self, value: store.state[keyPath: subscriptions] ) } }