import SwiftUI import AVFoundation import Photos struct AppPermissionsView: View { @EnvironmentObject private var languageManager: LanguageManager @State private var cameraPermissionStatus: AVAuthorizationStatus = .notDetermined @State private var photoPermissionStatus: PHAuthorizationStatus = .notDetermined @State private var isRequestingCameraPermission = false @State private var isRequestingPhotoPermission = false @State private var showPermissionDeniedAlert = false @State private var deniedPermissionType = "" var body: some View { ZStack { // 背景渐变 LinearGradient( gradient: Gradient(colors: [ Color(.systemBackground), Color(.systemGray6).opacity(0.2) ]), startPoint: .top, endPoint: .bottom ) .ignoresSafeArea() ScrollView { VStack(spacing: 24) { // 顶部图标 VStack(spacing: 16) { ZStack { Circle() .fill( LinearGradient( gradient: Gradient(colors: [ Color.blue.opacity(0.1), Color.blue.opacity(0.05) ]), startPoint: .topLeading, endPoint: .bottomTrailing ) ) .frame(width: 80, height: 80) Image(systemName: "lock.shield") .font(.system(size: 36, weight: .light)) .foregroundColor(.blue) } } .padding(.top, 20) // 权限说明卡片 VStack(alignment: .leading, spacing: 16) { HStack { Image(systemName: "info.circle") .font(.system(size: 20, weight: .medium)) .foregroundColor(.blue) .frame(width: 32) Text("permissions_info".localized) .font(.system(size: 18, weight: .semibold)) Spacer() } Text("permissions_description".localized) .font(.system(size: 14)) .foregroundColor(.secondary) .lineLimit(nil) } .padding(20) .background( RoundedRectangle(cornerRadius: 16) .fill(Color(.systemBackground)) .shadow(color: .black.opacity(0.05), radius: 8, x: 0, y: 2) ) .padding(.horizontal, 20) // 相机权限卡片 PermissionCard( icon: "camera.fill", iconColor: .blue, title: "camera_permission".localized, description: "camera_permission_description".localized, status: cameraPermissionStatus.displayText, statusColor: cameraPermissionStatus.statusColor, action: { requestCameraPermission() }, actionTitle: cameraPermissionStatus.actionTitle, isLoading: isRequestingCameraPermission ) // 相册权限卡片 PermissionCard( icon: "photo.fill", iconColor: .green, title: "photo_permission".localized, description: "photo_permission_description".localized, status: photoPermissionStatus.displayText, statusColor: photoPermissionStatus.statusColor, action: { requestPhotoPermission() }, actionTitle: photoPermissionStatus.actionTitle, isLoading: isRequestingPhotoPermission ) Spacer(minLength: 30) } } } .navigationTitle("app_permissions".localized) .navigationBarTitleDisplayMode(.inline) .onAppear { checkPermissions() } .onReceive(NotificationCenter.default.publisher(for: UIApplication.willEnterForegroundNotification)) { _ in // 当应用从后台返回前台时,重新检查权限状态 checkPermissions() } .alert("permission_denied_title".localized, isPresented: $showPermissionDeniedAlert) { Button("cancel".localized, role: .cancel) { } Button("open_settings".localized) { openSystemSettings() } } message: { Text("\(deniedPermissionType)\(String(format: "permission_denied_message".localized, deniedPermissionType))") } } // MARK: - 权限检查 private func checkPermissions() { cameraPermissionStatus = AVCaptureDevice.authorizationStatus(for: .video) photoPermissionStatus = PHPhotoLibrary.authorizationStatus() } // MARK: - 请求相机权限 private func requestCameraPermission() { logInfo("🔐 请求相机权限", className: "AppPermissionsView") // 设置加载状态 isRequestingCameraPermission = true AVCaptureDevice.requestAccess(for: .video) { granted in DispatchQueue.main.async { // 清除加载状态 self.isRequestingCameraPermission = false if granted { logInfo("✅ 相机权限请求成功", className: "AppPermissionsView") self.cameraPermissionStatus = .authorized // 成功触觉反馈 let successFeedback = UINotificationFeedbackGenerator() successFeedback.notificationOccurred(.success) } else { logWarning("❌ 相机权限请求被拒绝", className: "AppPermissionsView") self.cameraPermissionStatus = .denied // 显示权限被拒绝提示 self.deniedPermissionType = "camera".localized self.showPermissionDeniedAlert = true // 错误触觉反馈 let errorFeedback = UINotificationFeedbackGenerator() errorFeedback.notificationOccurred(.error) } } } } // MARK: - 请求相册权限 private func requestPhotoPermission() { logInfo("🔐 请求相册权限", className: "AppPermissionsView") // 设置加载状态 isRequestingPhotoPermission = true PHPhotoLibrary.requestAuthorization { status in DispatchQueue.main.async { // 清除加载状态 self.isRequestingPhotoPermission = false logInfo("📸 相册权限状态更新: \(status.rawValue)", className: "AppPermissionsView") self.photoPermissionStatus = status // 根据权限状态提供不同的反馈 switch status { case .authorized, .limited: // 成功触觉反馈 let successFeedback = UINotificationFeedbackGenerator() successFeedback.notificationOccurred(.success) case .denied, .restricted: // 显示权限被拒绝提示 self.deniedPermissionType = "photo".localized self.showPermissionDeniedAlert = true // 错误触觉反馈 let errorFeedback = UINotificationFeedbackGenerator() errorFeedback.notificationOccurred(.error) case .notDetermined: // 普通触觉反馈 let impactFeedback = UIImpactFeedbackGenerator(style: .medium) impactFeedback.impactOccurred() @unknown default: break } } } } // MARK: - 打开系统设置 private func openSystemSettings() { logInfo("⚙️ 打开系统设置", className: "AppPermissionsView") if let settingsUrl = URL(string: UIApplication.openSettingsURLString) { UIApplication.shared.open(settingsUrl) { success in if success { logInfo("✅ 成功打开系统设置", className: "AppPermissionsView") } else { logWarning("⚠️ 打开系统设置失败", className: "AppPermissionsView") } } } else { logError("❌ 无法创建系统设置URL", className: "AppPermissionsView") } } } // MARK: - 权限卡片组件 struct PermissionCard: View { let icon: String let iconColor: Color let title: String let description: String let status: String let statusColor: Color let action: () -> Void let actionTitle: String let isLoading: Bool @State private var isButtonPressed = false var body: some View { VStack(alignment: .leading, spacing: 16) { HStack { Image(systemName: icon) .font(.system(size: 20, weight: .medium)) .foregroundColor(iconColor) .frame(width: 32) VStack(alignment: .leading, spacing: 4) { Text(title) .font(.system(size: 18, weight: .semibold)) Text(description) .font(.system(size: 14)) .foregroundColor(.secondary) } Spacer() } HStack { Text(status) .font(.system(size: 14, weight: .medium)) .foregroundColor(statusColor) Spacer() Button(action: { // 添加按钮按下动画 withAnimation(.easeInOut(duration: 0.1)) { isButtonPressed = true } // 执行权限请求 action() // 重置按钮状态 DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { withAnimation(.easeInOut(duration: 0.1)) { isButtonPressed = false } } }) { HStack(spacing: 6) { if isLoading { ProgressView() .scaleEffect(0.8) .progressViewStyle(CircularProgressViewStyle(tint: .white)) } else if actionTitle == "request_permission".localized { Image(systemName: "hand.raised.fill") .font(.system(size: 12, weight: .medium)) } else if actionTitle == "open_settings".localized { Image(systemName: "gear") .font(.system(size: 12, weight: .medium)) } else if actionTitle == "permission_granted".localized { Image(systemName: "checkmark.circle.fill") .font(.system(size: 12, weight: .medium)) } else { Image(systemName: "exclamationmark.triangle.fill") .font(.system(size: 12, weight: .medium)) } Text(isLoading ? "requesting_permission".localized : actionTitle) .font(.system(size: 14, weight: .medium)) } .foregroundColor(.white) .padding(.horizontal, 16) .padding(.vertical, 8) .background( RoundedRectangle(cornerRadius: 6) .fill(statusColor) .scaleEffect(isButtonPressed ? 0.95 : 1.0) ) } .disabled(actionTitle == "permission_granted".localized || isLoading) .opacity((actionTitle == "permission_granted".localized || isLoading) ? 0.6 : 1.0) } } .padding(20) .background( RoundedRectangle(cornerRadius: 16) .fill(Color(.systemBackground)) .shadow(color: .black.opacity(0.05), radius: 8, x: 0, y: 2) ) .padding(.horizontal, 20) } } // MARK: - 权限状态扩展 extension AVAuthorizationStatus { var displayText: String { switch self { case .notDetermined: return "not_determined".localized case .restricted: return "restricted".localized case .denied: return "denied".localized case .authorized: return "authorized".localized @unknown default: return "unknown".localized } } var statusColor: Color { switch self { case .notDetermined: return .orange case .restricted, .denied: return .red case .authorized: return .green @unknown default: return .gray } } var actionTitle: String { switch self { case .notDetermined: return "request_permission".localized case .restricted, .denied: return "open_settings".localized case .authorized: return "permission_granted".localized @unknown default: return "unknown".localized } } var canRequestPermission: Bool { switch self { case .notDetermined: return true case .restricted, .denied, .authorized: return false @unknown default: return false } } } extension PHAuthorizationStatus { var displayText: String { switch self { case .notDetermined: return "not_determined".localized case .restricted: return "restricted".localized case .denied: return "denied".localized case .authorized: return "authorized".localized case .limited: return "limited".localized @unknown default: return "unknown".localized } } var statusColor: Color { switch self { case .notDetermined: return .orange case .restricted, .denied: return .red case .authorized, .limited: return .green @unknown default: return .gray } } var actionTitle: String { switch self { case .notDetermined: return "request_permission".localized case .restricted, .denied: return "open_settings".localized case .authorized, .limited: return "permission_granted".localized @unknown default: return "unknown".localized } } var canRequestPermission: Bool { switch self { case .notDetermined: return true case .restricted, .denied, .authorized, .limited: return false @unknown default: return false } } } #Preview { NavigationView { AppPermissionsView() .environmentObject(LanguageManager.shared) } }