RadrootsApplePermissionStatus.swift (6490B)
1 import Foundation 2 3 #if canImport(AVFoundation) 4 import AVFoundation 5 #endif 6 7 #if canImport(CoreLocation) 8 import CoreLocation 9 #endif 10 11 #if canImport(Photos) 12 import Photos 13 #endif 14 15 #if canImport(UserNotifications) 16 import UserNotifications 17 #endif 18 19 public struct RadrootsApplePermissionStatusAdapters: Sendable { 20 public let now: @Sendable () -> Date 21 public let notificationStatus: @Sendable () async -> RadrootsPermissionStatus 22 public let cameraStatus: @Sendable () -> RadrootsPermissionStatus 23 public let photosStatus: @Sendable () -> RadrootsPermissionStatus 24 public let microphoneStatus: @Sendable () -> RadrootsPermissionStatus 25 public let locationStatus: @Sendable () -> RadrootsPermissionStatus 26 27 public init( 28 now: @escaping @Sendable () -> Date = Date.init, 29 notificationStatus: @escaping @Sendable () async -> RadrootsPermissionStatus, 30 cameraStatus: @escaping @Sendable () -> RadrootsPermissionStatus, 31 photosStatus: @escaping @Sendable () -> RadrootsPermissionStatus, 32 microphoneStatus: @escaping @Sendable () -> RadrootsPermissionStatus, 33 locationStatus: @escaping @Sendable () -> RadrootsPermissionStatus 34 ) { 35 self.now = now 36 self.notificationStatus = notificationStatus 37 self.cameraStatus = cameraStatus 38 self.photosStatus = photosStatus 39 self.microphoneStatus = microphoneStatus 40 self.locationStatus = locationStatus 41 } 42 43 public static var live: Self { 44 Self( 45 notificationStatus: { 46 await Self.currentNotificationStatus() 47 }, 48 cameraStatus: { 49 Self.currentCameraStatus() 50 }, 51 photosStatus: { 52 Self.currentPhotosStatus() 53 }, 54 microphoneStatus: { 55 Self.currentMicrophoneStatus() 56 }, 57 locationStatus: { 58 Self.currentLocationStatus() 59 } 60 ) 61 } 62 63 public static func currentNotificationStatus() async -> RadrootsPermissionStatus { 64 #if canImport(UserNotifications) 65 return await withCheckedContinuation { continuation in 66 UNUserNotificationCenter.current().getNotificationSettings { settings in 67 continuation.resume(returning: Self.permissionStatus(for: settings.authorizationStatus)) 68 } 69 } 70 #else 71 return .unsupported 72 #endif 73 } 74 75 public static func currentCameraStatus() -> RadrootsPermissionStatus { 76 #if canImport(AVFoundation) 77 return permissionStatus(for: AVCaptureDevice.authorizationStatus(for: .video)) 78 #else 79 return .unsupported 80 #endif 81 } 82 83 public static func currentPhotosStatus() -> RadrootsPermissionStatus { 84 #if canImport(Photos) 85 return permissionStatus(for: PHPhotoLibrary.authorizationStatus(for: .readWrite)) 86 #else 87 return .unsupported 88 #endif 89 } 90 91 public static func currentMicrophoneStatus() -> RadrootsPermissionStatus { 92 #if canImport(AVFoundation) 93 return permissionStatus(for: AVCaptureDevice.authorizationStatus(for: .audio)) 94 #else 95 return .unsupported 96 #endif 97 } 98 99 public static func currentLocationStatus() -> RadrootsPermissionStatus { 100 #if canImport(CoreLocation) 101 guard CLLocationManager.locationServicesEnabled() else { 102 return .unavailable 103 } 104 return permissionStatus(for: CLLocationManager().authorizationStatus) 105 #else 106 return .unsupported 107 #endif 108 } 109 110 #if canImport(UserNotifications) 111 public static func permissionStatus(for authorizationStatus: UNAuthorizationStatus) -> RadrootsPermissionStatus { 112 switch authorizationStatus { 113 case .notDetermined: 114 .notDetermined 115 case .denied: 116 .denied 117 case .authorized: 118 .authorized 119 case .provisional: 120 .limited 121 case .ephemeral: 122 .limited 123 @unknown default: 124 .unavailable 125 } 126 } 127 #endif 128 129 #if canImport(AVFoundation) 130 public static func permissionStatus(for authorizationStatus: AVAuthorizationStatus) -> RadrootsPermissionStatus { 131 switch authorizationStatus { 132 case .notDetermined: 133 .notDetermined 134 case .restricted: 135 .restricted 136 case .denied: 137 .denied 138 case .authorized: 139 .authorized 140 @unknown default: 141 .unavailable 142 } 143 } 144 #endif 145 146 #if canImport(Photos) 147 public static func permissionStatus(for authorizationStatus: PHAuthorizationStatus) -> RadrootsPermissionStatus { 148 switch authorizationStatus { 149 case .notDetermined: 150 .notDetermined 151 case .restricted: 152 .restricted 153 case .denied: 154 .denied 155 case .authorized: 156 .authorized 157 case .limited: 158 .limited 159 @unknown default: 160 .unavailable 161 } 162 } 163 #endif 164 165 #if canImport(CoreLocation) 166 public static func permissionStatus(for authorizationStatus: CLAuthorizationStatus) -> RadrootsPermissionStatus { 167 switch authorizationStatus { 168 case .notDetermined: 169 .notDetermined 170 case .restricted: 171 .restricted 172 case .denied: 173 .denied 174 case .authorizedAlways: 175 .authorized 176 #if os(iOS) 177 case .authorizedWhenInUse: 178 .authorized 179 #endif 180 @unknown default: 181 .unavailable 182 } 183 } 184 #endif 185 } 186 187 public final class RadrootsApplePermissionStatusProvider: RadrootsPermissionStatusProvider, Sendable { 188 private let adapters: RadrootsApplePermissionStatusAdapters 189 190 public init(adapters: RadrootsApplePermissionStatusAdapters = .live) { 191 self.adapters = adapters 192 } 193 194 public func snapshot(for kind: RadrootsPermissionKind) async throws -> RadrootsPermissionSnapshot { 195 let status = switch kind { 196 case .notifications: 197 await adapters.notificationStatus() 198 case .camera: 199 adapters.cameraStatus() 200 case .photos: 201 adapters.photosStatus() 202 case .microphone: 203 adapters.microphoneStatus() 204 case .location: 205 adapters.locationStatus() 206 } 207 return RadrootsPermissionSnapshot(kind: kind, status: status, observedAt: adapters.now()) 208 } 209 }