diff --git a/apps/ios/Feature/Shared/AppPermissionCenter.swift b/apps/ios/Feature/Shared/AppPermissionCenter.swift index 84991f4d..d85d09e5 100644 --- a/apps/ios/Feature/Shared/AppPermissionCenter.swift +++ b/apps/ios/Feature/Shared/AppPermissionCenter.swift @@ -41,8 +41,18 @@ enum AppPermissionCenter { @MainActor static func notificationStatus() async -> AppPermissionStatus { - let settings = await UNUserNotificationCenter.current().notificationSettings() - return mapNotificationSettings(settings) + await currentNotificationStatus() + } + + /// Maps inside the callback so only the Sendable status crosses actors. Xcode 16's SDK + /// doesn't mark `UNNotificationSettings` Sendable, so awaiting `notificationSettings()` from + /// the main actor fails the Swift 6 build there. + private static func currentNotificationStatus() async -> AppPermissionStatus { + await withCheckedContinuation { continuation in + UNUserNotificationCenter.current().getNotificationSettings { settings in + continuation.resume(returning: mapNotificationSettings(settings)) + } + } } @MainActor @@ -102,8 +112,7 @@ enum AppPermissionCenter { } case .notifications: - let settings = await UNUserNotificationCenter.current().notificationSettings() - switch mapNotificationSettings(settings) { + switch await currentNotificationStatus() { case .authorized, .limited: return .granted case .denied, .restricted: diff --git a/apps/ios/Platform/Notifications/NotificationScheduler.swift b/apps/ios/Platform/Notifications/NotificationScheduler.swift index adfb35c8..eacf7752 100644 --- a/apps/ios/Platform/Notifications/NotificationScheduler.swift +++ b/apps/ios/Platform/Notifications/NotificationScheduler.swift @@ -19,10 +19,7 @@ actor NotificationScheduler { permissionStatus: AppPermissionStatus ) async throws { let knownMealIdentifiers = NotificationRuleKind.orderedMealKinds.map(\.notificationIdentifier) - let pending = await center.pendingNotificationRequests() - let staleFreshnessIdentifiers = - pending - .map(\.identifier) + let staleFreshnessIdentifiers = await center.pendingNotificationIdentifiers() .filter { $0.hasPrefix("freshness.") } await center.removePendingNotificationRequests( diff --git a/apps/ios/Platform/Notifications/UserNotificationCenterClient.swift b/apps/ios/Platform/Notifications/UserNotificationCenterClient.swift index b22d5545..5cae5a7a 100644 --- a/apps/ios/Platform/Notifications/UserNotificationCenterClient.swift +++ b/apps/ios/Platform/Notifications/UserNotificationCenterClient.swift @@ -1,15 +1,21 @@ import Foundation import UserNotifications +/// Returns pending identifiers rather than `UNNotificationRequest`s: Xcode 16's SDK doesn't +/// mark the requests Sendable, so returning them to the scheduler actor fails the Swift 6 build. protocol UserNotificationCenterClient: Sendable { - func pendingNotificationRequests() async -> [UNNotificationRequest] + func pendingNotificationIdentifiers() async -> [String] func add(_ request: UNNotificationRequest) async throws func removePendingNotificationRequests(withIdentifiers identifiers: [String]) async } struct SystemUserNotificationCenterClient: UserNotificationCenterClient { - func pendingNotificationRequests() async -> [UNNotificationRequest] { - await UNUserNotificationCenter.current().pendingNotificationRequests() + func pendingNotificationIdentifiers() async -> [String] { + await withCheckedContinuation { continuation in + UNUserNotificationCenter.current().getPendingNotificationRequests { requests in + continuation.resume(returning: requests.map(\.identifier)) + } + } } func add(_ request: UNNotificationRequest) async throws { diff --git a/apps/ios/Tests/NotificationSchedulerTests.swift b/apps/ios/Tests/NotificationSchedulerTests.swift index e2ba4ab6..2088015b 100644 --- a/apps/ios/Tests/NotificationSchedulerTests.swift +++ b/apps/ios/Tests/NotificationSchedulerTests.swift @@ -127,8 +127,8 @@ final class MockNotificationCenter: @unchecked Sendable, UserNotificationCenterC self.pendingRequests = pendingRequests } - func pendingNotificationRequests() async -> [UNNotificationRequest] { - pendingRequests + func pendingNotificationIdentifiers() async -> [String] { + pendingRequests.map(\.identifier) } func add(_ request: UNNotificationRequest) async throws {