@ -6,6 +6,10 @@ struct AppPermissionsView: View {
@ EnvironmentObject private var languageManager : LanguageManager
@ EnvironmentObject private var languageManager : LanguageManager
@ State private var cameraPermissionStatus : AVAuthorizationStatus = . notDetermined
@ State private var cameraPermissionStatus : AVAuthorizationStatus = . notDetermined
@ State private var photoPermissionStatus : PHAuthorizationStatus = . 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 {
var body : some View {
ZStack {
ZStack {
@ -83,7 +87,8 @@ struct AppPermissionsView: View {
action : {
action : {
requestCameraPermission ( )
requestCameraPermission ( )
} ,
} ,
actionTitle : cameraPermissionStatus . actionTitle
actionTitle : cameraPermissionStatus . actionTitle ,
isLoading : isRequestingCameraPermission
)
)
// 相 册 权 限 卡 片
// 相 册 权 限 卡 片
@ -97,54 +102,9 @@ struct AppPermissionsView: View {
action : {
action : {
requestPhotoPermission ( )
requestPhotoPermission ( )
} ,
} ,
actionTitle : photoPermissionStatus . actionTitle
actionTitle : photoPermissionStatus . actionTitle ,
isLoading : isRequestingPhotoPermission
)
)
// 系 统 设 置 卡 片
VStack ( alignment : . leading , spacing : 16 ) {
HStack {
Image ( systemName : " gearshape.fill " )
. font ( . system ( size : 20 , weight : . medium ) )
. foregroundColor ( . orange )
. frame ( width : 32 )
Text ( " system_settings " . localized )
. font ( . system ( size : 18 , weight : . semibold ) )
Spacer ( )
}
Text ( " system_settings_description " . localized )
. font ( . system ( size : 14 ) )
. foregroundColor ( . secondary )
. lineLimit ( nil )
Button ( action : {
openSystemSettings ( )
} ) {
HStack {
Image ( systemName : " arrow.up.right.square " )
. font ( . system ( size : 16 , weight : . medium ) )
Text ( " open_system_settings " . localized )
. font ( . system ( size : 16 , weight : . medium ) )
}
. foregroundColor ( . white )
. padding ( . horizontal , 20 )
. padding ( . vertical , 12 )
. background (
RoundedRectangle ( cornerRadius : 8 )
. fill ( Color . blue )
)
}
}
. padding ( 20 )
. background (
RoundedRectangle ( cornerRadius : 16 )
. fill ( Color ( . systemBackground ) )
. shadow ( color : . black . opacity ( 0.05 ) , radius : 8 , x : 0 , y : 2 )
)
. padding ( . horizontal , 20 )
Spacer ( minLength : 30 )
Spacer ( minLength : 30 )
}
}
}
}
@ -154,6 +114,18 @@ struct AppPermissionsView: View {
. onAppear {
. onAppear {
checkPermissions ( )
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: - 权 限 检 查
// MARK: - 权 限 检 查
@ -164,26 +136,93 @@ struct AppPermissionsView: View {
// MARK: - 请 求 相 机 权 限
// MARK: - 请 求 相 机 权 限
private func requestCameraPermission ( ) {
private func requestCameraPermission ( ) {
logInfo ( " 🔐 请求相机权限 " , className : " AppPermissionsView " )
// 设 置 加 载 状 态
isRequestingCameraPermission = true
AVCaptureDevice . requestAccess ( for : . video ) { granted in
AVCaptureDevice . requestAccess ( for : . video ) { granted in
DispatchQueue . main . async {
DispatchQueue . main . async {
self . cameraPermissionStatus = granted ? . authorized : . denied
// 清 除 加 载 状 态
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: - 请 求 相 册 权 限
// MARK: - 请 求 相 册 权 限
private func requestPhotoPermission ( ) {
private func requestPhotoPermission ( ) {
logInfo ( " 🔐 请求相册权限 " , className : " AppPermissionsView " )
// 设 置 加 载 状 态
isRequestingPhotoPermission = true
PHPhotoLibrary . requestAuthorization { status in
PHPhotoLibrary . requestAuthorization { status in
DispatchQueue . main . async {
DispatchQueue . main . async {
// 清 除 加 载 状 态
self . isRequestingPhotoPermission = false
logInfo ( " 📸 相册权限状态更新: \( status . rawValue ) " , className : " AppPermissionsView " )
self . photoPermissionStatus = status
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: - 打 开 系 统 设 置
// MARK: - 打 开 系 统 设 置
private func openSystemSettings ( ) {
private func openSystemSettings ( ) {
logInfo ( " ⚙️ 打开系统设置 " , className : " AppPermissionsView " )
if let settingsUrl = URL ( string : UIApplication . openSettingsURLString ) {
if let settingsUrl = URL ( string : UIApplication . openSettingsURLString ) {
UIApplication . shared . open ( settingsUrl )
UIApplication . shared . open ( settingsUrl ) { success in
if success {
logInfo ( " ✅ 成功打开系统设置 " , className : " AppPermissionsView " )
} else {
logWarning ( " ⚠️ 打开系统设置失败 " , className : " AppPermissionsView " )
}
}
} else {
logError ( " ❌ 无法创建系统设置URL " , className : " AppPermissionsView " )
}
}
}
}
}
}
@ -198,6 +237,9 @@ struct PermissionCard: View {
let statusColor : Color
let statusColor : Color
let action : ( ) -> Void
let action : ( ) -> Void
let actionTitle : String
let actionTitle : String
let isLoading : Bool
@ State private var isButtonPressed = false
var body : some View {
var body : some View {
VStack ( alignment : . leading , spacing : 16 ) {
VStack ( alignment : . leading , spacing : 16 ) {
@ -225,17 +267,55 @@ struct PermissionCard: View {
Spacer ( )
Spacer ( )
Button ( action : action ) {
Button ( action : {
Text ( actionTitle )
// 添 加 按 钮 按 下 动 画
. font ( . system ( size : 14 , weight : . medium ) )
withAnimation ( . easeInOut ( duration : 0.1 ) ) {
. foregroundColor ( . white )
isButtonPressed = true
. padding ( . horizontal , 16 )
}
. padding ( . vertical , 8 )
. background (
// 执 行 权 限 请 求
RoundedRectangle ( cornerRadius : 6 )
action ( )
. fill ( statusColor )
)
// 重 置 按 钮 状 态
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 )
. padding ( 20 )
@ -290,6 +370,17 @@ extension AVAuthorizationStatus {
return " unknown " . localized
return " unknown " . localized
}
}
}
}
var canRequestPermission : Bool {
switch self {
case . notDetermined :
return true
case . restricted , . denied , . authorized :
return false
@ unknown default :
return false
}
}
}
}
extension PHAuthorizationStatus {
extension PHAuthorizationStatus {
@ -335,6 +426,17 @@ extension PHAuthorizationStatus {
return " unknown " . localized
return " unknown " . localized
}
}
}
}
var canRequestPermission : Bool {
switch self {
case . notDetermined :
return true
case . restricted , . denied , . authorized , . limited :
return false
@ unknown default :
return false
}
}
}
}
# Preview {
# Preview {