|
|
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)
|
|
|
}
|
|
|
}
|