Files
xianyan/macos/Runner/PermissionManager.swift
Developer 88a3f6d65f feat: 新增仪表盘页面与macOS多项优化
1. 新增TDashboard翻译类型与多语言文案
2. 完善macOS权限管理与Impeller渲染适配
3. 更新服务器部署配置与协议文件上传脚本
4. 修复翻译导入服务与根类型编译问题
2026-06-26 06:34:05 +08:00

275 lines
9.9 KiB
Swift
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/// ============================================================
/// APP macOS
/// : 2026-06-26
/// : 2026-06-26
/// : macOS permission_handler_apple macOS
/// camera / microphone / photos / notification
/// notDetermined / granted / permanentlyDenied / restricted
/// : AppDelegate.swift 使
/// ============================================================
import Cocoa
import AVFoundation
import Photos
import UserNotifications
/// macOS
///
/// TCC
/// AppDelegate MethodChannelapps.xy.xianyan/macos.app
/// Dart 使 permission_handler_apple macOS
///
/// Dart `AppPermission.macosPermissionName`
/// - `camera` AVCaptureDevice
/// - `microphone` AVCaptureDevice
/// - `photos` PHPhotoLibrary
/// - `notification` UNUserNotificationCenter
///
/// Dart `AppPermissionStatus`
/// - `notDetermined`
/// - `granted`
/// - `permanentlyDenied`
/// - `restricted` /MDM
class PermissionManager {
// ============================================================
// MARK: -
// ============================================================
///
private static let supportedPermissions: Set<String> = [
"camera", "microphone", "photos", "notification"
]
// ============================================================
// MARK: -
// ============================================================
///
///
/// - Parameters:
/// - permission: camera / microphone / photos / notification
/// - completion: notDetermined / granted / permanentlyDenied / restricted
static func checkStatus(_ permission: String, completion: @escaping (String) -> Void) {
guard supportedPermissions.contains(permission) else {
NSLog("[PermissionManager] checkStatus: 不支持的权限 '\(permission)',返回 granted")
completion("granted")
return
}
switch permission {
case "camera":
completion(avAuthorizationStatus(for: .video))
case "microphone":
completion(avAuthorizationStatus(for: .audio))
case "photos":
completion(photosAuthorizationStatus())
case "notification":
//
UNUserNotificationCenter.current().getNotificationSettings { settings in
DispatchQueue.main.async {
completion(notificationAuthorizationStatus(from: settings.authorizationStatus))
}
}
default:
completion("granted")
}
}
// ============================================================
// MARK: -
// ============================================================
/// TCC
///
/// - Parameters:
/// - permission: camera / microphone / photos / notification
/// - completion:
static func requestPermission(_ permission: String, completion: @escaping (String) -> Void) {
guard supportedPermissions.contains(permission) else {
NSLog("[PermissionManager] requestPermission: 不支持的权限 '\(permission)',返回 granted")
completion("granted")
return
}
switch permission {
case "camera":
requestAVAuthorization(for: .video, completion: completion)
case "microphone":
requestAVAuthorization(for: .audio, completion: completion)
case "photos":
requestPhotosAuthorization(completion: completion)
case "notification":
requestNotificationAuthorization(completion: completion)
default:
completion("granted")
}
}
// ============================================================
// MARK: -
// ============================================================
/// -
///
/// macOS iOS
///
/// - Parameter permission: nil
static func openSystemSettings(_ permission: String?) {
// macOS Ventura(13)+ 使 URL scheme
// x-apple.systempreferences:com.apple.preference.security?Privacy
let urlString = "x-apple.systempreferences:com.apple.preference.security?Privacy"
if let url = URL(string: urlString) {
NSWorkspace.shared.open(url)
NSLog("[PermissionManager] openSystemSettings: 已打开隐私设置(权限: \(permission ?? "nil")")
} else {
NSLog("[PermissionManager] openSystemSettings: URL 构建失败")
}
}
// ============================================================
// MARK: - AVFoundation camera / microphone
// ============================================================
/// AVAuthorizationStatus
private static func avAuthorizationStatus(for mediaType: AVMediaType) -> String {
switch AVCaptureDevice.authorizationStatus(for: mediaType) {
case .authorized:
return "granted"
case .notDetermined:
return "notDetermined"
case .denied:
return "permanentlyDenied"
case .restricted:
return "restricted"
@unknown default:
return "permanentlyDenied"
}
}
/// AVFoundation camera / microphone
private static func requestAVAuthorization(
for mediaType: AVMediaType,
completion: @escaping (String) -> Void
) {
//
let currentStatus = AVCaptureDevice.authorizationStatus(for: mediaType)
if currentStatus == .authorized {
completion("granted")
return
}
if currentStatus == .restricted {
completion("restricted")
return
}
if currentStatus == .denied {
completion("permanentlyDenied")
return
}
// notDetermined TCC
AVCaptureDevice.requestAccess(for: mediaType) { granted in
DispatchQueue.main.async {
completion(granted ? "granted" : "permanentlyDenied")
}
}
}
// ============================================================
// MARK: - Photos
// ============================================================
/// PHAuthorizationStatus
private static func photosAuthorizationStatus() -> String {
switch PHPhotoLibrary.authorizationStatus(for: .readWrite) {
case .authorized:
return "granted"
case .notDetermined:
return "notDetermined"
case .denied:
return "permanentlyDenied"
case .restricted:
return "restricted"
case .limited:
// limited
return "granted"
@unknown default:
return "permanentlyDenied"
}
}
/// Photos
private static func requestPhotosAuthorization(completion: @escaping (String) -> Void) {
let currentStatus = PHPhotoLibrary.authorizationStatus(for: .readWrite)
if currentStatus == .authorized || currentStatus == .limited {
completion("granted")
return
}
if currentStatus == .restricted {
completion("restricted")
return
}
if currentStatus == .denied {
completion("permanentlyDenied")
return
}
// notDetermined TCC
PHPhotoLibrary.requestAuthorization(for: .readWrite) { status in
DispatchQueue.main.async {
switch status {
case .authorized, .limited:
completion("granted")
case .denied:
completion("permanentlyDenied")
case .restricted:
completion("restricted")
case .notDetermined:
completion("notDetermined")
@unknown default:
completion("permanentlyDenied")
}
}
}
}
// ============================================================
// MARK: -
// ============================================================
/// UNAuthorizationStatus
private static func notificationAuthorizationStatus(from status: UNAuthorizationStatus) -> String {
switch status {
case .authorized:
return "granted"
case .notDetermined:
return "notDetermined"
case .denied:
return "permanentlyDenied"
case .provisional:
// provisional
return "granted"
@unknown default:
return "permanentlyDenied"
}
}
///
private static func requestNotificationAuthorization(completion: @escaping (String) -> Void) {
let center = UNUserNotificationCenter.current()
center.getNotificationSettings { settings in
//
if settings.authorizationStatus == .authorized
|| settings.authorizationStatus == .provisional {
DispatchQueue.main.async { completion("granted") }
return
}
if settings.authorizationStatus == .denied {
DispatchQueue.main.async { completion("permanentlyDenied") }
return
}
// notDetermined
center.requestAuthorization(options: [.alert, .sound, .badge]) { granted, _ in
DispatchQueue.main.async {
completion(granted ? "granted" : "permanentlyDenied")
}
}
}
}
}