|
|
import SwiftUI
|
|
|
import AVFoundation
|
|
|
import AudioToolbox
|
|
|
import Combine
|
|
|
|
|
|
// MARK: - 主扫描视图
|
|
|
struct ScannerView: View {
|
|
|
@StateObject private var scannerViewModel = ScannerViewModel()
|
|
|
@State private var showPreviewPause = false
|
|
|
@State private var selectedScanningStyle: ScanningLineStyle = .modern
|
|
|
@State private var screenOrientation = UIDevice.current.orientation
|
|
|
@State private var previewLayer: AVCaptureVideoPreviewLayer?
|
|
|
|
|
|
var body: some View {
|
|
|
ZStack {
|
|
|
// 相机权限检查
|
|
|
if scannerViewModel.cameraAuthorizationStatus == .authorized {
|
|
|
// 相机预览层
|
|
|
CameraPreviewView(session: scannerViewModel.captureSession, previewLayer: $previewLayer)
|
|
|
.ignoresSafeArea()
|
|
|
|
|
|
// 扫描界面覆盖层
|
|
|
ScanningOverlayView(
|
|
|
showPreviewPause: showPreviewPause,
|
|
|
selectedStyle: $selectedScanningStyle,
|
|
|
detectedCodesCount: scannerViewModel.detectedCodes.count
|
|
|
)
|
|
|
|
|
|
// 条码位置标记覆盖层
|
|
|
if showPreviewPause && !scannerViewModel.detectedCodes.isEmpty {
|
|
|
CodePositionOverlay(
|
|
|
detectedCodes: scannerViewModel.detectedCodes,
|
|
|
previewLayer: previewLayer,
|
|
|
onCodeSelected: handleCodeSelection
|
|
|
)
|
|
|
}
|
|
|
|
|
|
// 测试按钮(调试用)
|
|
|
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()
|
|
|
}
|
|
|
)
|
|
|
}
|
|
|
}
|
|
|
.navigationTitle("扫描器")
|
|
|
.navigationBarTitleDisplayMode(.inline)
|
|
|
.navigationBarBackButtonHidden(false)
|
|
|
.toolbar {
|
|
|
ToolbarItem(placement: .navigationBarLeading) {
|
|
|
// 手电筒按钮 - 只在相机权限已授权时显示
|
|
|
if scannerViewModel.cameraAuthorizationStatus == .authorized && scannerViewModel.isTorchAvailable {
|
|
|
Button(action: {
|
|
|
logInfo("🔦 用户点击手电筒按钮", className: "ScannerView")
|
|
|
|
|
|
// 添加触觉反馈
|
|
|
let impactFeedback = UIImpactFeedbackGenerator(style: .medium)
|
|
|
impactFeedback.impactOccurred()
|
|
|
|
|
|
scannerViewModel.toggleTorch()
|
|
|
}) {
|
|
|
Image(systemName: scannerViewModel.isTorchOn ? "bolt.fill" : "bolt")
|
|
|
.font(.system(size: 18, weight: .semibold))
|
|
|
.foregroundColor(scannerViewModel.isTorchOn ? .yellow : .blue)
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
ToolbarItem(placement: .navigationBarTrailing) {
|
|
|
// 重新扫描按钮 - 只在预览暂停状态时显示
|
|
|
if showPreviewPause {
|
|
|
Button(action: {
|
|
|
logInfo("🔄 用户点击工具栏重新扫描按钮", className: "ScannerView")
|
|
|
|
|
|
// 添加触觉反馈
|
|
|
let impactFeedback = UIImpactFeedbackGenerator(style: .medium)
|
|
|
impactFeedback.impactOccurred()
|
|
|
|
|
|
resetToScanning()
|
|
|
}) {
|
|
|
HStack(spacing: 6) {
|
|
|
Image(systemName: "arrow.clockwise")
|
|
|
.font(.system(size: 16, weight: .semibold))
|
|
|
|
|
|
Text("rescan_button".localized)
|
|
|
.font(.system(size: 14, weight: .medium))
|
|
|
}
|
|
|
.foregroundColor(.blue)
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
.onAppear {
|
|
|
// 只有在相机权限已授权时才启动扫描
|
|
|
if scannerViewModel.cameraAuthorizationStatus == .authorized {
|
|
|
scannerViewModel.startScanning()
|
|
|
}
|
|
|
}
|
|
|
.onDisappear {
|
|
|
scannerViewModel.stopScanning()
|
|
|
// 确保退出时关闭手电筒
|
|
|
if scannerViewModel.isTorchOn {
|
|
|
scannerViewModel.turnOffTorch()
|
|
|
}
|
|
|
}
|
|
|
.alert("scan_error_title".localized, isPresented: $scannerViewModel.showAlert) {
|
|
|
Button("OK") { }
|
|
|
} message: {
|
|
|
Text("scan_error_message".localized)
|
|
|
}
|
|
|
.onReceive(scannerViewModel.$detectedCodes) { codes in
|
|
|
handleDetectedCodes(codes)
|
|
|
}
|
|
|
.onReceive(scannerViewModel.$cameraAuthorizationStatus) { status in
|
|
|
if status == .authorized {
|
|
|
logInfo("🎯 相机权限已授权,启动扫描", className: "ScannerView")
|
|
|
scannerViewModel.startScanning()
|
|
|
}
|
|
|
}
|
|
|
.onReceive(NotificationCenter.default.publisher(for: UIDevice.orientationDidChangeNotification)) { _ in
|
|
|
handleOrientationChange()
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// MARK: - 私有方法
|
|
|
|
|
|
private func handleDetectedCodes(_ codes: [DetectedCode]) {
|
|
|
guard !codes.isEmpty else { return }
|
|
|
|
|
|
logInfo("检测到条码数量: \(codes.count)", className: "ScannerView")
|
|
|
|
|
|
if codes.count == 1 {
|
|
|
logInfo("单个条码,准备自动选择", className: "ScannerView")
|
|
|
pauseForPreview()
|
|
|
autoSelectSingleCode(code: codes[0])
|
|
|
} else {
|
|
|
logInfo("多个条码,等待用户选择", className: "ScannerView")
|
|
|
pauseForPreview()
|
|
|
}
|
|
|
}
|
|
|
|
|
|
private func handleOrientationChange() {
|
|
|
screenOrientation = UIDevice.current.orientation
|
|
|
logInfo("Screen orientation changed to: \(screenOrientation.rawValue)", className: "ScannerView")
|
|
|
}
|
|
|
|
|
|
private func handleCodeSelection(_ selectedCode: DetectedCode) {
|
|
|
logInfo("🎯 ScannerView 收到条码选择回调", className: "ScannerView")
|
|
|
logInfo(" 选择的条码ID: \(selectedCode.id)", className: "ScannerView")
|
|
|
logInfo(" 选择的条码类型: \(selectedCode.type)", className: "ScannerView")
|
|
|
logInfo(" 选择的条码内容: \(selectedCode.content)", className: "ScannerView")
|
|
|
logInfo(" 选择的条码位置: \(selectedCode.bounds)", className: "ScannerView")
|
|
|
|
|
|
let formattedResult = "类型: \(selectedCode.type)\n内容: \(selectedCode.content)"
|
|
|
logInfo(" 格式化结果: \(formattedResult)", className: "ScannerView")
|
|
|
|
|
|
NotificationCenter.default.post(name: .scannerDidScanCode, object: formattedResult)
|
|
|
// 使用导航返回,不需要手动dismiss
|
|
|
}
|
|
|
|
|
|
private func pauseForPreview() {
|
|
|
showPreviewPause = true
|
|
|
}
|
|
|
|
|
|
private func resetToScanning() {
|
|
|
logInfo("🔄 ScannerView 开始重置到扫描状态", className: "ScannerView")
|
|
|
|
|
|
// 重置UI状态
|
|
|
showPreviewPause = false
|
|
|
|
|
|
// 重置扫描状态并重新开始
|
|
|
scannerViewModel.resetDetection()
|
|
|
scannerViewModel.restartScanning()
|
|
|
|
|
|
// 延迟检查会话状态
|
|
|
DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) {
|
|
|
logInfo("🔍 检查扫描会话状态", className: "ScannerView")
|
|
|
self.scannerViewModel.checkSessionStatus()
|
|
|
}
|
|
|
|
|
|
logInfo("✅ ScannerView 已重置到扫描状态", className: "ScannerView")
|
|
|
}
|
|
|
|
|
|
private func autoSelectSingleCode(code: DetectedCode) {
|
|
|
logInfo("开始自动选择定时器,条码类型: \(code.type)", className: "ScannerView")
|
|
|
|
|
|
DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) {
|
|
|
guard self.showPreviewPause && self.scannerViewModel.detectedCodes.count == 1 else {
|
|
|
logInfo("条件不满足,取消自动选择", className: "ScannerView")
|
|
|
return
|
|
|
}
|
|
|
|
|
|
logInfo("条件满足,执行自动选择", className: "ScannerView")
|
|
|
self.handleCodeSelection(code)
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
#if DEBUG
|
|
|
struct ScannerView_Previews: PreviewProvider {
|
|
|
static var previews: some View {
|
|
|
NavigationView {
|
|
|
ScannerView()
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
#endif |