Files
smart-city-digital-twin-mar…/smart-app-city/frontend/node_modules/expo-camera/ios/Current/BarcodeScanner.swift
Eric FELIXINE e30ae8ed09 feat(smart-app): implement complete mobile app MVP
- App.tsx: full navigation (Auth stack + Main tabs with 5 screens)
- Auth: LoginScreen, RegisterScreen, ForgotPasswordScreen
- HomeScreen: dashboard with IoT metrics, weather widget, alerts, quick actions, sensors
- MapScreen: interactive map with layer toggles (6 layers)
- MarketplaceScreen: categories (6), products (5), search
- ChatScreen: AI chat with quick prompts (4), bot responses
- ProfileScreen: user info, stats, menu (9 items), logout
- AlertsScreen: alert list with severity, acknowledge
- SensorsScreen: sensor list with type filters (6 types), search
- ZonesScreen: zone cards with stats
- SettingsScreen: language picker (FR/EN/ES/DE), privacy, about
- Stores: iotStore (sensors, zones, alerts), notificationStore, uiStore + i18n
- Hooks: useSensors, useAlerts, useNotifications, useLocation
- Components: Card, Button, LoadingSpinner, ErrorBoundary, Header
- Services: iotService, notificationService (with axios API client)
- Utils: formatters (temp, AQI, noise, dates), validators (email, password, IBAN)
- Theme: colors.ts with full design system (Blue Ocean palette)
- Ditto: fixed MongoDB connection, new JWT secrets, official gateway image
2026-06-01 18:00:35 -04:00

235 lines
6.9 KiB
Swift

import ZXingObjC
import AVFoundation
let BARCODE_TYPES_KEY = "barcodeTypes"
class BarcodeScanner: NSObject, AVCaptureMetadataOutputObjectsDelegate, AVCaptureVideoDataOutputSampleBufferDelegate {
var onBarcodeScanned: (([String: Any]?) -> Void)?
var isScanningBarcodes = false
// MARK: - Properties
private let session: AVCaptureSession
private let sessionQueue: DispatchQueue
private let zxingCaptureQueue = DispatchQueue(label: "com.zxing.captureQueue")
private var metadataOutput: AVCaptureMetadataOutput?
private var videoDataOutput: AVCaptureVideoDataOutput?
private var settings = BarcodeScannerUtils.getDefaultSettings()
private var zxingBarcodeReaders: [AVMetadataObject.ObjectType: ZXReader] = [
AVMetadataObject.ObjectType.pdf417: ZXPDF417Reader(),
AVMetadataObject.ObjectType.code39: ZXCode39Reader()
]
private var previewLayer: AVCaptureVideoPreviewLayer?
private var zxingFPSProcessed = 6.0
private var zxingEnabled = true
init(session: AVCaptureSession, sessionQueue: DispatchQueue) {
self.session = session
self.sessionQueue = sessionQueue
if #available(iOS 15.4, *) {
zxingBarcodeReaders[AVMetadataObject.ObjectType.codabar] = ZXCodaBarReader()
}
}
func setSettings(_ newSettings: [String: [AVMetadataObject.ObjectType]]) {
for (key, value) in newSettings where key == BARCODE_TYPES_KEY {
let previousTypes = Set(settings[BARCODE_TYPES_KEY] ?? [])
let newTypes = Set(value)
if previousTypes != newTypes {
settings[BARCODE_TYPES_KEY] = value
let zxingCoveredTypes = Set(zxingBarcodeReaders.keys)
zxingEnabled = !zxingCoveredTypes.isDisjoint(with: newTypes)
sessionQueue.async {
self.maybeStartBarcodeScanning()
}
}
}
}
func setPreviewLayer(layer: AVCaptureVideoPreviewLayer) {
self.previewLayer = layer
}
func setIsEnabled(_ enabled: Bool) {
guard isScanningBarcodes != enabled else {
return
}
isScanningBarcodes = enabled
sessionQueue.async {
if self.isScanningBarcodes {
if self.metadataOutput != nil {
self.setConnection(enabled: true)
} else {
self.maybeStartBarcodeScanning()
}
} else {
self.setConnection(enabled: false)
}
}
}
func setConnection(enabled: Bool) {
metadataOutput?.connections.forEach {
$0.isEnabled = enabled
}
}
func maybeStartBarcodeScanning() {
guard isScanningBarcodes else {
return
}
if metadataOutput == nil || videoDataOutput == nil {
addOutputs()
if metadataOutput == nil {
return
}
}
let availableObjectTypes: [AVMetadataObject.ObjectType] = metadataOutput?.availableMetadataObjectTypes ?? []
let requestedTypes = (settings[BARCODE_TYPES_KEY] ?? []).filter {
availableObjectTypes.contains($0)
}
metadataOutput?.metadataObjectTypes = requestedTypes
}
func stopBarcodeScanning() {
removeOutputs()
if isScanningBarcodes {
onBarcodeScanned?(nil)
}
}
func scanBarcodes(from image: CGImage, completion: @escaping (ZXResult) -> Void) {
let source = ZXCGImageLuminanceSource(cgImage: image)
let binarizer = ZXHybridBinarizer(source: source)
let bitmap = ZXBinaryBitmap(binarizer: binarizer)
var result: ZXResult?
for reader in zxingBarcodeReaders.values {
result = try? reader.decode(bitmap, hints: nil)
if result != nil {
break
}
}
if result == nil && bitmap?.rotateSupported == true {
if let rotatedBitmap = bitmap?.rotateCounterClockwise() {
for reader in zxingBarcodeReaders.values {
result = try? reader.decode(rotatedBitmap, hints: nil)
if result != nil {
break
}
}
}
}
if let result {
completion(result)
}
}
private func addOutputs() {
session.beginConfiguration()
if metadataOutput == nil {
let output = AVCaptureMetadataOutput()
output.setMetadataObjectsDelegate(self, queue: sessionQueue)
if session.canAddOutput(output) {
session.addOutput(output)
metadataOutput = output
}
}
if videoDataOutput == nil {
let output = AVCaptureVideoDataOutput()
output.videoSettings = [kCVPixelBufferPixelFormatTypeKey as String: kCVPixelFormatType_32BGRA]
output.alwaysDiscardsLateVideoFrames = true
output.setSampleBufferDelegate(self, queue: zxingCaptureQueue)
if session.canAddOutput(output) {
session.addOutput(output)
videoDataOutput = output
}
}
session.commitConfiguration()
}
private func removeOutputs() {
session.beginConfiguration()
if let metadataOutput {
if session.outputs.contains(metadataOutput) {
session.removeOutput(metadataOutput)
self.metadataOutput = nil
}
}
if let videoDataOutput {
if session.outputs.contains(videoDataOutput) {
session.removeOutput(videoDataOutput)
self.videoDataOutput = nil
}
}
session.commitConfiguration()
}
func metadataOutput(_ output: AVCaptureMetadataOutput, didOutput metadataObjects: [AVMetadataObject], from connection: AVCaptureConnection) {
guard let settings = settings[BARCODE_TYPES_KEY], let metadataOutput else {
return
}
for metadata in metadataObjects {
var codeMetadata = metadata as? AVMetadataMachineReadableCodeObject
if let previewLayer {
codeMetadata = previewLayer.transformedMetadataObject(for: metadata) as? AVMetadataMachineReadableCodeObject
}
for barcodeType in settings {
if zxingBarcodeReaders[barcodeType] != nil {
continue
}
if let codeMetadata {
if codeMetadata.stringValue != nil && codeMetadata.type == barcodeType {
onBarcodeScanned?(BarcodeScannerUtils.avMetadataCodeObjectToDictionary(codeMetadata))
}
}
}
}
}
func captureOutput(_ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) {
guard let barcodeTypes = settings[BARCODE_TYPES_KEY],
let metadataOutput,
zxingEnabled else {
return
}
let kMinMargin = 1.0 / zxingFPSProcessed
let presentTimeStamp = CMSampleBufferGetPresentationTimeStamp(sampleBuffer)
var curFrameTimeStamp = 0.0
var lastFrameTimeStamp = 0.0
curFrameTimeStamp = Double(presentTimeStamp.value) / Double(presentTimeStamp.timescale)
if curFrameTimeStamp - lastFrameTimeStamp > Double(kMinMargin) {
lastFrameTimeStamp = curFrameTimeStamp
if let videoFrame = CMSampleBufferGetImageBuffer(sampleBuffer),
let videoFrameImage = ZXCGImageLuminanceSource.createImage(from: videoFrame) {
self.scanBarcodes(from: videoFrameImage) { barcodeScannerResult in
self.onBarcodeScanned?(BarcodeScannerUtils.zxResultToDictionary(barcodeScannerResult))
}
}
}
}
}