Implement camera permission management in ScannerView; add CameraPermissionView for user feedback on permission status; enhance logging for permission requests and status changes; localize new strings for camera permission prompts.

main
v504 2 months ago
parent eabfef4969
commit d90fc5a034

@ -16,8 +16,8 @@
endingColumnNumber = "9223372036854775807" endingColumnNumber = "9223372036854775807"
startingLineNumber = "146" startingLineNumber = "146"
endingLineNumber = "146" endingLineNumber = "146"
landmarkName = "ScanningOverlayView" landmarkName = "resetToScanning()"
landmarkType = "14"> landmarkType = "7">
</BreakpointContent> </BreakpointContent>
</BreakpointProxy> </BreakpointProxy>
<BreakpointProxy <BreakpointProxy
@ -32,8 +32,8 @@
endingColumnNumber = "9223372036854775807" endingColumnNumber = "9223372036854775807"
startingLineNumber = "147" startingLineNumber = "147"
endingLineNumber = "147" endingLineNumber = "147"
landmarkName = "ScanningOverlayView" landmarkName = "resetToScanning()"
landmarkType = "14"> landmarkType = "7">
</BreakpointContent> </BreakpointContent>
</BreakpointProxy> </BreakpointProxy>
<BreakpointProxy <BreakpointProxy
@ -48,8 +48,8 @@
endingColumnNumber = "9223372036854775807" endingColumnNumber = "9223372036854775807"
startingLineNumber = "139" startingLineNumber = "139"
endingLineNumber = "139" endingLineNumber = "139"
landmarkName = "ScannerView" landmarkName = "resetToScanning()"
landmarkType = "14"> landmarkType = "7">
</BreakpointContent> </BreakpointContent>
</BreakpointProxy> </BreakpointProxy>
<BreakpointProxy <BreakpointProxy
@ -64,8 +64,8 @@
endingColumnNumber = "9223372036854775807" endingColumnNumber = "9223372036854775807"
startingLineNumber = "167" startingLineNumber = "167"
endingLineNumber = "167" endingLineNumber = "167"
landmarkName = "body" landmarkName = "ScanningOverlayView"
landmarkType = "24"> landmarkType = "14">
</BreakpointContent> </BreakpointContent>
</BreakpointProxy> </BreakpointProxy>
</Breakpoints> </Breakpoints>

@ -15,38 +15,55 @@ struct ScannerView: View {
var body: some View { var body: some View {
ZStack { ZStack {
// //
CameraPreviewView(session: scannerViewModel.captureSession, previewLayer: $previewLayer) if scannerViewModel.cameraAuthorizationStatus == .authorized {
.ignoresSafeArea() //
CameraPreviewView(session: scannerViewModel.captureSession, previewLayer: $previewLayer)
// .ignoresSafeArea()
ScanningOverlayView(
showPreviewPause: showPreviewPause, //
selectedStyle: $selectedScanningStyle, ScanningOverlayView(
detectedCodesCount: scannerViewModel.detectedCodes.count, showPreviewPause: showPreviewPause,
onClose: { dismiss() } selectedStyle: $selectedScanningStyle,
) detectedCodesCount: scannerViewModel.detectedCodes.count,
onClose: { dismiss() }
//
if showPreviewPause && !scannerViewModel.detectedCodes.isEmpty {
CodePositionOverlay(
detectedCodes: scannerViewModel.detectedCodes,
previewLayer: previewLayer,
onCodeSelected: handleCodeSelection,
onRescan: resetToScanning
) )
}
// //
if showPreviewPause && scannerViewModel.detectedCodes.count == 1 { if showPreviewPause && !scannerViewModel.detectedCodes.isEmpty {
TestAutoSelectButton( CodePositionOverlay(
detectedCode: scannerViewModel.detectedCodes[0], detectedCodes: scannerViewModel.detectedCodes,
onSelect: handleCodeSelection previewLayer: previewLayer,
onCodeSelected: handleCodeSelection,
onRescan: resetToScanning
)
}
//
if showPreviewPause && scannerViewModel.detectedCodes.count == 1 {
TestAutoSelectButton(
detectedCode: scannerViewModel.detectedCodes[0],
onSelect: handleCodeSelection
)
}
} else {
// UI
CameraPermissionView(
authorizationStatus: scannerViewModel.cameraAuthorizationStatus,
onRequestPermission: {
scannerViewModel.refreshCameraPermission()
},
onOpenSettings: {
scannerViewModel.openSettings()
}
) )
} }
} }
.onAppear { .onAppear {
scannerViewModel.startScanning() //
if scannerViewModel.cameraAuthorizationStatus == .authorized {
scannerViewModel.startScanning()
}
} }
.onDisappear { .onDisappear {
scannerViewModel.stopScanning() scannerViewModel.stopScanning()
@ -59,6 +76,12 @@ struct ScannerView: View {
.onReceive(scannerViewModel.$detectedCodes) { codes in .onReceive(scannerViewModel.$detectedCodes) { codes in
handleDetectedCodes(codes) handleDetectedCodes(codes)
} }
.onReceive(scannerViewModel.$cameraAuthorizationStatus) { status in
if status == .authorized {
logInfo("🎯 相机权限已授权,启动扫描", className: "ScannerView")
scannerViewModel.startScanning()
}
}
.onReceive(NotificationCenter.default.publisher(for: UIDevice.orientationDidChangeNotification)) { _ in .onReceive(NotificationCenter.default.publisher(for: UIDevice.orientationDidChangeNotification)) { _ in
handleOrientationChange() handleOrientationChange()
} }
@ -311,13 +334,78 @@ struct CameraPreviewView: UIViewRepresentable {
class ScannerViewModel: NSObject, ObservableObject, AVCaptureMetadataOutputObjectsDelegate { class ScannerViewModel: NSObject, ObservableObject, AVCaptureMetadataOutputObjectsDelegate {
@Published var detectedCodes: [DetectedCode] = [] @Published var detectedCodes: [DetectedCode] = []
@Published var showAlert = false @Published var showAlert = false
@Published var cameraAuthorizationStatus: AVAuthorizationStatus = .notDetermined
@Published var showPermissionAlert = false
var captureSession: AVCaptureSession! var captureSession: AVCaptureSession!
private var metadataOutput: AVCaptureMetadataOutput? private var metadataOutput: AVCaptureMetadataOutput?
override init() { override init() {
super.init() super.init()
setupCaptureSession() checkCameraPermission()
}
// MARK: -
private func checkCameraPermission() {
logInfo("🔍 检查相机权限状态", className: "ScannerViewModel")
switch AVCaptureDevice.authorizationStatus(for: .video) {
case .authorized:
logInfo("✅ 相机权限已授权", className: "ScannerViewModel")
cameraAuthorizationStatus = .authorized
setupCaptureSession()
case .notDetermined:
logInfo("❓ 相机权限未确定,请求权限", className: "ScannerViewModel")
requestCameraPermission()
case .denied, .restricted:
logWarning("❌ 相机权限被拒绝或受限", className: "ScannerViewModel")
cameraAuthorizationStatus = AVCaptureDevice.authorizationStatus(for: .video)
showPermissionAlert = true
@unknown default:
logWarning("❓ 未知的相机权限状态", className: "ScannerViewModel")
cameraAuthorizationStatus = .notDetermined
}
}
private func requestCameraPermission() {
logInfo("🔐 请求相机权限", className: "ScannerViewModel")
AVCaptureDevice.requestAccess(for: .video) { [weak self] granted in
DispatchQueue.main.async {
if granted {
logInfo("✅ 相机权限请求成功", className: "ScannerViewModel")
self?.cameraAuthorizationStatus = .authorized
self?.setupCaptureSession()
} else {
logWarning("❌ 相机权限请求被拒绝", className: "ScannerViewModel")
self?.cameraAuthorizationStatus = .denied
self?.showPermissionAlert = true
}
}
}
}
func openSettings() {
logInfo("⚙️ 打开系统设置", className: "ScannerViewModel")
if let settingsUrl = URL(string: UIApplication.openSettingsURLString) {
UIApplication.shared.open(settingsUrl) { success in
if success {
logInfo("✅ 成功打开系统设置", className: "ScannerViewModel")
} else {
logWarning("⚠️ 打开系统设置失败", className: "ScannerViewModel")
}
}
}
}
func refreshCameraPermission() {
logInfo("🔍 重新检查相机权限状态", className: "ScannerViewModel")
checkCameraPermission()
} }
// MARK: - // MARK: -
@ -882,6 +970,85 @@ extension Notification.Name {
static let scannerDidScanCode = Notification.Name("scannerDidScanCode") static let scannerDidScanCode = Notification.Name("scannerDidScanCode")
} }
// MARK: -
struct CameraPermissionView: View {
let authorizationStatus: AVAuthorizationStatus
let onRequestPermission: () -> Void
let onOpenSettings: () -> Void
var body: some View {
VStack(spacing: 30) {
Spacer()
//
Image(systemName: "camera.fill")
.font(.system(size: 80))
.foregroundColor(.gray)
//
Text("camera_permission_title".localized)
.font(.largeTitle)
.fontWeight(.bold)
.multilineTextAlignment(.center)
//
Text(getDescriptionText())
.font(.body)
.multilineTextAlignment(.center)
.foregroundColor(.secondary)
.padding(.horizontal, 40)
//
VStack(spacing: 15) {
if authorizationStatus == .notDetermined {
Button(action: onRequestPermission) {
HStack {
Image(systemName: "camera.badge.ellipsis")
Text("request_camera_permission".localized)
}
.font(.headline)
.foregroundColor(.white)
.frame(maxWidth: .infinity)
.padding()
.background(Color.blue)
.cornerRadius(12)
}
} else if authorizationStatus == .denied || authorizationStatus == .restricted {
Button(action: onOpenSettings) {
HStack {
Image(systemName: "gear")
Text("open_settings".localized)
}
.font(.headline)
.foregroundColor(.white)
.frame(maxWidth: .infinity)
.padding()
.background(Color.orange)
.cornerRadius(12)
}
}
}
.padding(.horizontal, 40)
Spacer()
}
.background(Color(.systemBackground))
}
private func getDescriptionText() -> String {
switch authorizationStatus {
case .notDetermined:
return "camera_permission_description".localized
case .denied:
return "camera_permission_denied".localized
case .restricted:
return "camera_permission_restricted".localized
default:
return "camera_permission_unknown".localized
}
}
}
#if DEBUG #if DEBUG
struct ScannerView_Previews: PreviewProvider { struct ScannerView_Previews: PreviewProvider {
static var previews: some View { static var previews: some View {

@ -34,6 +34,15 @@
// Test Button // Test Button
"test_auto_select" = "Test Auto Select"; "test_auto_select" = "Test Auto Select";
// Camera Permission
"camera_permission_title" = "Camera Permission Required";
"camera_permission_description" = "This app needs access to your camera to scan QR codes and barcodes. Please grant camera permission to continue.";
"camera_permission_denied" = "Camera access has been denied. Please enable camera permission in Settings to use the scanner.";
"camera_permission_restricted" = "Camera access is restricted. Please check your device settings or contact your administrator.";
"camera_permission_unknown" = "Camera permission status is unknown. Please check your device settings.";
"request_camera_permission" = "Grant Camera Access";
"open_settings" = "Open Settings";
// Language Settings // Language Settings
"select_language" = "Select Language"; "select_language" = "Select Language";
"language_changes_info" = "Language changes will take effect immediately"; "language_changes_info" = "Language changes will take effect immediately";

@ -34,6 +34,15 @@
// 测试按钮 // 测试按钮
"test_auto_select" = "测试自动选择"; "test_auto_select" = "测试自动选择";
// 相机权限
"camera_permission_title" = "需要相机权限";
"camera_permission_description" = "此应用需要访问您的相机来扫描二维码和条形码。请授予相机权限以继续使用。";
"camera_permission_denied" = "相机访问被拒绝。请在设置中启用相机权限以使用扫描器。";
"camera_permission_restricted" = "相机访问受限。请检查您的设备设置或联系管理员。";
"camera_permission_unknown" = "相机权限状态未知。请检查您的设备设置。";
"request_camera_permission" = "授予相机权限";
"open_settings" = "打开设置";
// 语言设置 // 语言设置
"select_language" = "选择语言"; "select_language" = "选择语言";
"language_changes_info" = "语言更改将立即生效"; "language_changes_info" = "语言更改将立即生效";

Loading…
Cancel
Save