diff --git a/CHANGELOG.md b/CHANGELOG.md index 5685aaead..6bf221af1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,14 @@ +## 7.0.0-beta.1 + +This version replaces MLKit on iOS with Apples Vision API. The code base is now shared with macOS. +This change removes the requirement for iOS 15. + +There are still some problems with this build. +- Zoom slider not working +- scanWindow not working +- Flash shows briefly when starting scanner. +- Other issues, not fully tested yet. + ## 6.0.2 Bugs fixed: diff --git a/android/gradle/wrapper/gradle-wrapper.properties b/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 000000000..09523c0e5 --- /dev/null +++ b/android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,7 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip +networkTimeout=10000 +validateDistributionUrl=true +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/macos/mobile_scanner.podspec b/darwin/mobile_scanner.podspec similarity index 73% rename from macos/mobile_scanner.podspec rename to darwin/mobile_scanner.podspec index 1aa906528..2d162dfef 100644 --- a/macos/mobile_scanner.podspec +++ b/darwin/mobile_scanner.podspec @@ -4,7 +4,7 @@ # Pod::Spec.new do |s| s.name = 'mobile_scanner' - s.version = '6.0.2' + s.version = '7.0.0' s.summary = 'An universal scanner for Flutter based on MLKit.' s.description = <<-DESC An universal scanner for Flutter based on MLKit. @@ -12,11 +12,17 @@ An universal scanner for Flutter based on MLKit. s.homepage = 'https://github.com/juliansteenbakker/mobile_scanner' s.license = { :file => '../LICENSE' } s.author = { 'Julian Steenbakker' => 'juliansteenbakker@outlook.com' } + + s.source = { :path => '.' } s.source_files = 'mobile_scanner/Sources/mobile_scanner/**/*.swift' - s.dependency 'FlutterMacOS' - s.platform = :osx, '10.14' + s.ios.dependency 'Flutter' + s.osx.dependency 'FlutterMacOS' + s.ios.deployment_target = '12.0' + s.osx.deployment_target = '10.14' + + s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES' } s.swift_version = '5.0' - s.resource_bundles = {'mobile_scanner_macos_privacy' => ['mobile_scanner/Sources/mobile_scanner/Resources/PrivacyInfo.xcprivacy']} + s.resource_bundles = {'mobile_scanner_privacy' => ['mobile_scanner/Sources/mobile_scanner/Resources/PrivacyInfo.xcprivacy']} end diff --git a/macos/mobile_scanner/Package.swift b/darwin/mobile_scanner/Package.swift similarity index 100% rename from macos/mobile_scanner/Package.swift rename to darwin/mobile_scanner/Package.swift diff --git a/ios/Classes/DetectionSpeed.swift b/darwin/mobile_scanner/Sources/mobile_scanner/DetectionSpeed.swift similarity index 100% rename from ios/Classes/DetectionSpeed.swift rename to darwin/mobile_scanner/Sources/mobile_scanner/DetectionSpeed.swift diff --git a/macos/mobile_scanner/Sources/mobile_scanner/MobileScannerErrorCodes.swift b/darwin/mobile_scanner/Sources/mobile_scanner/MobileScannerErrorCodes.swift similarity index 100% rename from macos/mobile_scanner/Sources/mobile_scanner/MobileScannerErrorCodes.swift rename to darwin/mobile_scanner/Sources/mobile_scanner/MobileScannerErrorCodes.swift diff --git a/macos/mobile_scanner/Sources/mobile_scanner/MobileScannerPlugin.swift b/darwin/mobile_scanner/Sources/mobile_scanner/MobileScannerPlugin.swift similarity index 88% rename from macos/mobile_scanner/Sources/mobile_scanner/MobileScannerPlugin.swift rename to darwin/mobile_scanner/Sources/mobile_scanner/MobileScannerPlugin.swift index 8261d367c..d7cce030a 100644 --- a/macos/mobile_scanner/Sources/mobile_scanner/MobileScannerPlugin.swift +++ b/darwin/mobile_scanner/Sources/mobile_scanner/MobileScannerPlugin.swift @@ -1,9 +1,17 @@ import AVFoundation -import FlutterMacOS +import Flutter import Vision -import AppKit import VideoToolbox +#if os(iOS) + import Flutter + import UIKit + import MobileCoreServices +#else + import AppKit + import FlutterMacOS +#endif + public class MobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHandler, FlutterTexture, AVCaptureVideoDataOutputSampleBufferDelegate { let registry: FlutterTextureRegistry @@ -39,11 +47,23 @@ public class MobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHandler, var position = AVCaptureDevice.Position.back public static func register(with registrar: FlutterPluginRegistrar) { - let instance = MobileScannerPlugin(registrar.textures) + #if os(iOS) + let textures = registrar.textures() + #else + let textures = registrar.textures + #endif + + #if os(iOS) + let messenger = registrar.messenger() + #else + let messenger = registrar.messenger + #endif + + let instance = MobileScannerPlugin(textures) let method = FlutterMethodChannel(name: - "dev.steenbakker.mobile_scanner/scanner/method", binaryMessenger: registrar.messenger) + "dev.steenbakker.mobile_scanner/scanner/method", binaryMessenger: messenger) let event = FlutterEventChannel(name: - "dev.steenbakker.mobile_scanner/scanner/event", binaryMessenger: registrar.messenger) + "dev.steenbakker.mobile_scanner/scanner/event", binaryMessenger: messenger) registrar.addMethodCallDelegate(instance, channel: method) event.setStreamHandler(instance) } @@ -267,6 +287,26 @@ public class MobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHandler, let scaledScanWindow = CGRect(x: minX, y: minY, width: width, height: height) return scaledScanWindow.contains(barcode.boundingBox) } + + private func getVideoOrientation() -> AVCaptureVideoOrientation { + var videoOrientation: AVCaptureVideoOrientation + + switch UIDevice.current.orientation { + case .portrait: + videoOrientation = .portrait + case .portraitUpsideDown: + videoOrientation = .portraitUpsideDown + case .landscapeLeft: + videoOrientation = .landscapeRight + case .landscapeRight: + videoOrientation = .landscapeLeft + default: + videoOrientation = .portrait + } + + return videoOrientation + } + func start(_ call: FlutterMethodCall, _ result: @escaping FlutterResult) { if (device != nil || captureSession != nil) { @@ -327,30 +367,59 @@ public class MobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHandler, return } captureSession!.sessionPreset = AVCaptureSession.Preset.photo - // Add video output. - let videoOutput = AVCaptureVideoDataOutput() + + + // Add video output + let videoOutput = AVCaptureVideoDataOutput() videoOutput.videoSettings = [kCVPixelBufferPixelFormatTypeKey as String: kCVPixelFormatType_32BGRA] videoOutput.alwaysDiscardsLateVideoFrames = true - + videoOutput.setSampleBufferDelegate(self, queue: DispatchQueue.main) captureSession!.addOutput(videoOutput) - for connection in videoOutput.connections { + let orientation = self.getVideoOrientation() + + // Adjust orientation for the video connection + if let connection = videoOutput.connections.first { + if connection.isVideoOrientationSupported { + connection.videoOrientation = orientation + } + if position == .front && connection.isVideoMirroringSupported { connection.isVideoMirrored = true } } + captureSession!.commitConfiguration() - captureSession!.startRunning() - let dimensions = CMVideoFormatDescriptionGetDimensions(device.activeFormat.formatDescription) - let size = ["width": Double(dimensions.width), "height": Double(dimensions.height)] - - let answer: [String : Any?] = [ - "textureId": textureId, - "size": size, - "currentTorchState": device.hasTorch ? device.torchMode.rawValue : -1, - ] - result(answer) + + // Move startRunning to a background thread to avoid blocking the main UI thread + DispatchQueue.global(qos: .background).async { + self.captureSession!.startRunning() + + DispatchQueue.main.async { + // Return the result on the main thread after the session starts + let dimensions = CMVideoFormatDescriptionGetDimensions(self.device!.activeFormat.formatDescription) + var width = Double(dimensions.width) + var height = Double(dimensions.height) + + // Swap width and height if the image is in portrait mode + if orientation == AVCaptureVideoOrientation.portrait || orientation == AVCaptureVideoOrientation.portraitUpsideDown { + let temp = width + width = height + height = temp + } + + let size = ["width": width, "height": height] + + let answer: [String : Any?] = [ + "textureId": self.textureId, + "size": size, + "currentTorchState": self.device!.hasTorch ? self.device!.torchMode.rawValue : -1, + ] + + result(answer) // Make sure to return the result on the main thread + } + } } // TODO: this method should be removed when iOS and MacOS share their implementation. @@ -587,7 +656,7 @@ extension CGImage { let formatHint: CFString - if #available(macOS 11.0, *) { + if #available(iOS 14.0, macOS 11.0, *) { formatHint = UTType.jpeg.identifier as CFString } else { formatHint = kUTTypeJPEG @@ -626,6 +695,7 @@ extension VNBarcodeObservation { ], "format": symbology.toInt ?? -1, "rawValue": payloadStringValue ?? "", + "displayValue": payloadStringValue ?? "", "size": [ "width": distanceBetween(topLeft, topRight), "height": distanceBetween(topLeft, bottomLeft), @@ -636,7 +706,7 @@ extension VNBarcodeObservation { extension VNBarcodeSymbology { static func fromInt(_ mapValue:Int) -> VNBarcodeSymbology? { - if #available(macOS 12.0, *) { + if #available(iOS 15.0, macOS 12.0, *) { if(mapValue == 8){ return VNBarcodeSymbology.codabar } @@ -670,7 +740,7 @@ extension VNBarcodeSymbology { } var toInt: Int? { - if #available(macOS 12.0, *) { + if #available(iOS 15.0, macOS 12.0, *) { if(self == VNBarcodeSymbology.codabar){ return 8 } diff --git a/ios/Resources/PrivacyInfo.xcprivacy b/darwin/mobile_scanner/Sources/mobile_scanner/Resources/PrivacyInfo.xcprivacy similarity index 100% rename from ios/Resources/PrivacyInfo.xcprivacy rename to darwin/mobile_scanner/Sources/mobile_scanner/Resources/PrivacyInfo.xcprivacy diff --git a/example/ios/Podfile b/example/ios/Podfile index d9ee07199..885313b06 100644 --- a/example/ios/Podfile +++ b/example/ios/Podfile @@ -1,5 +1,5 @@ # Uncomment this line to define a global platform for your project -# platform :ios, '15.5.0' +platform :ios, '12.0' # CocoaPods analytics sends network stats synchronously affecting flutter build latency. ENV['COCOAPODS_DISABLE_STATS'] = 'true' @@ -41,7 +41,7 @@ post_install do |installer| installer.pods_project.targets.each do |target| flutter_additional_ios_build_settings(target) target.build_configurations.each do |config| - config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '15.5.0' + config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '12.0' end end end diff --git a/example/ios/Runner.xcodeproj/project.pbxproj b/example/ios/Runner.xcodeproj/project.pbxproj index a0a72c483..86330a9b4 100644 --- a/example/ios/Runner.xcodeproj/project.pbxproj +++ b/example/ios/Runner.xcodeproj/project.pbxproj @@ -198,7 +198,6 @@ 9705A1C41CF9048500538489 /* Embed Frameworks */, 3B06AD1E1E4923F5004D2608 /* Thin Binary */, 3DBCC0215D7BED1D9A756EA3 /* [CP] Embed Pods Frameworks */, - BB0C8EA8DA81A75DE53F052F /* [CP] Copy Pods Resources */, ); buildRules = ( ); @@ -318,23 +317,6 @@ shellPath = /bin/sh; shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; }; - BB0C8EA8DA81A75DE53F052F /* [CP] Copy Pods Resources */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-input-files.xcfilelist", - ); - name = "[CP] Copy Pods Resources"; - outputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-output-files.xcfilelist", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources.sh\"\n"; - showEnvVarsInLog = 0; - }; C7DE006A696F551C4E067E41 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; @@ -488,7 +470,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; - DEVELOPMENT_TEAM = ""; + DEVELOPMENT_TEAM = 75Y2P2WSQQ; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( @@ -670,7 +652,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; - DEVELOPMENT_TEAM = ""; + DEVELOPMENT_TEAM = 75Y2P2WSQQ; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( @@ -696,7 +678,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; - DEVELOPMENT_TEAM = ""; + DEVELOPMENT_TEAM = 75Y2P2WSQQ; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( diff --git a/ios/.gitignore b/ios/.gitignore deleted file mode 100644 index 0c885071e..000000000 --- a/ios/.gitignore +++ /dev/null @@ -1,38 +0,0 @@ -.idea/ -.vagrant/ -.sconsign.dblite -.svn/ - -.DS_Store -*.swp -profile - -DerivedData/ -build/ -GeneratedPluginRegistrant.h -GeneratedPluginRegistrant.m - -.generated/ - -*.pbxuser -*.mode1v3 -*.mode2v3 -*.perspectivev3 - -!default.pbxuser -!default.mode1v3 -!default.mode2v3 -!default.perspectivev3 - -xcuserdata - -*.moved-aside - -*.pyc -*sync/ -Icon? -.tags* - -/Flutter/Generated.xcconfig -/Flutter/ephemeral/ -/Flutter/flutter_export_environment.sh \ No newline at end of file diff --git a/ios/Assets/.gitkeep b/ios/Assets/.gitkeep deleted file mode 100644 index e69de29bb..000000000 diff --git a/ios/Classes/BarcodeHandler.swift b/ios/Classes/BarcodeHandler.swift deleted file mode 100644 index 8545fdbb9..000000000 --- a/ios/Classes/BarcodeHandler.swift +++ /dev/null @@ -1,44 +0,0 @@ -// -// BarcodeHandler.swift -// mobile_scanner -// -// Created by Julian Steenbakker on 24/08/2022. -// - -import Flutter -import Foundation - -public class BarcodeHandler: NSObject, FlutterStreamHandler { - private var eventSink: FlutterEventSink? - private let eventChannel: FlutterEventChannel - - init(registrar: FlutterPluginRegistrar) { - eventChannel = FlutterEventChannel(name: - "dev.steenbakker.mobile_scanner/scanner/event", binaryMessenger: registrar.messenger()) - super.init() - eventChannel.setStreamHandler(self) - } - - func publishError(_ error: FlutterError) { - DispatchQueue.main.async { - self.eventSink?(error) - } - } - - func publishEvent(_ event: [String: Any?]) { - DispatchQueue.main.async { - self.eventSink?(event) - } - } - - public func onListen(withArguments arguments: Any?, - eventSink: @escaping FlutterEventSink) -> FlutterError? { - self.eventSink = eventSink - return nil - } - - public func onCancel(withArguments arguments: Any?) -> FlutterError? { - eventSink = nil - return nil - } -} diff --git a/ios/Classes/MobileScanner.swift b/ios/Classes/MobileScanner.swift deleted file mode 100644 index cec1c52ed..000000000 --- a/ios/Classes/MobileScanner.swift +++ /dev/null @@ -1,488 +0,0 @@ -// -// MobileScanner.swift -// mobile_scanner -// -// Created by Julian Steenbakker on 15/02/2022. -// - -import Foundation -import Flutter -import AVFoundation -import MLKitVision -import MLKitBarcodeScanning - -typealias MobileScannerCallback = ((Array?, Error?, UIImage) -> ()) -typealias TorchModeChangeCallback = ((Int?) -> ()) -typealias ZoomScaleChangeCallback = ((Double?) -> ()) - -public class MobileScanner: NSObject, AVCaptureVideoDataOutputSampleBufferDelegate, FlutterTexture { - /// Capture session of the camera - var captureSession: AVCaptureSession? - - /// The selected camera - var device: AVCaptureDevice! - - /// The long lived barcode scanner for scanning barcodes from a camera preview. - var scanner: BarcodeScanner? = nil - - /// Default position of camera - var videoPosition: AVCaptureDevice.Position = AVCaptureDevice.Position.back - - /// When results are found, this callback will be called - let mobileScannerCallback: MobileScannerCallback - - /// When torch mode is changes, this callback will be called - let torchModeChangeCallback: TorchModeChangeCallback - - /// When zoom scale is changes, this callback will be called - let zoomScaleChangeCallback: ZoomScaleChangeCallback - - /// If provided, the Flutter registry will be used to send the output of the CaptureOutput to a Flutter texture. - private let registry: FlutterTextureRegistry? - - /// Image to be sent to the texture - var latestBuffer: CVImageBuffer! - - /// Texture id of the camera preview for Flutter - private var textureId: Int64! - - var detectionSpeed: DetectionSpeed = DetectionSpeed.noDuplicates - - private let backgroundQueue = DispatchQueue(label: "camera-handling") - - var standardZoomFactor: CGFloat = 1 - - private var nextScanTime = 0.0 - - private var imagesCurrentlyBeingProcessed = false - - public var timeoutSeconds: Double = 0 - - init(registry: FlutterTextureRegistry?, mobileScannerCallback: @escaping MobileScannerCallback, torchModeChangeCallback: @escaping TorchModeChangeCallback, zoomScaleChangeCallback: @escaping ZoomScaleChangeCallback) { - self.registry = registry - self.mobileScannerCallback = mobileScannerCallback - self.torchModeChangeCallback = torchModeChangeCallback - self.zoomScaleChangeCallback = zoomScaleChangeCallback - super.init() - } - - /// Get the default camera device for the given `position`. - /// - /// This function selects the most appropriate camera, when it is available. - private func getDefaultCameraDevice(position: AVCaptureDevice.Position) -> AVCaptureDevice? { - if #available(iOS 13.0, *) { - // Find the built-in Triple Camera, if it exists. - if let device = AVCaptureDevice.default(.builtInTripleCamera, - for: .video, - position: position) { - return device - } - - // Find the built-in Dual-Wide Camera, if it exists. - if let device = AVCaptureDevice.default(.builtInDualWideCamera, - for: .video, - position: position) { - return device - } - } - - // Find the built-in Dual Camera, if it exists. - if let device = AVCaptureDevice.default(.builtInDualCamera, - for: .video, - position: position) { - return device - } - - // Find the built-in Wide-Angle Camera, if it exists. - if let device = AVCaptureDevice.default(.builtInWideAngleCamera, - for: .video, - position: position) { - return device - } - - return nil - } - - /// Check if we already have camera permission. - func checkPermission() -> Int { - let status = AVCaptureDevice.authorizationStatus(for: .video) - switch status { - case .notDetermined: - return 0 - case .authorized: - return 1 - default: - return 2 - } - } - - /// Request permissions for video - func requestPermission(_ result: @escaping FlutterResult) { - AVCaptureDevice.requestAccess(for: .video, completionHandler: { result($0) }) - } - - /// Gets called when a new image is added to the buffer - public func captureOutput(_ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) { - guard let imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) else { - return - } - latestBuffer = imageBuffer - registry?.textureFrameAvailable(textureId) - - let currentTime = Date().timeIntervalSince1970 - let eligibleForScan = currentTime > nextScanTime && !imagesCurrentlyBeingProcessed - - if ((detectionSpeed == DetectionSpeed.normal || detectionSpeed == DetectionSpeed.noDuplicates) && eligibleForScan || detectionSpeed == DetectionSpeed.unrestricted) { - - nextScanTime = currentTime + timeoutSeconds - imagesCurrentlyBeingProcessed = true - - let ciImage = latestBuffer.image - - let image = VisionImage(image: ciImage) - image.orientation = imageOrientation( - deviceOrientation: UIDevice.current.orientation, - defaultOrientation: .portrait, - position: videoPosition - ) - - scanner?.process(image) { [self] barcodes, error in - imagesCurrentlyBeingProcessed = false - - if (detectionSpeed == DetectionSpeed.noDuplicates) { - let newScannedBarcodes = barcodes?.compactMap({ barcode in - return barcode.rawValue - }).sorted() - - if (error == nil && barcodesString != nil && newScannedBarcodes != nil && barcodesString!.elementsEqual(newScannedBarcodes!)) { - return - } - - if (newScannedBarcodes?.isEmpty == false) { - barcodesString = newScannedBarcodes - } - } - - mobileScannerCallback(barcodes, error, ciImage) - } - } - } - - /// Start scanning for barcodes - func start(barcodeScannerOptions: BarcodeScannerOptions?, cameraPosition: AVCaptureDevice.Position, torch: Bool, detectionSpeed: DetectionSpeed, completion: @escaping (MobileScannerStartParameters) -> ()) throws { - self.detectionSpeed = detectionSpeed - if (device != nil || captureSession != nil) { - throw MobileScannerError.alreadyStarted - } - - barcodesString = nil - scanner = barcodeScannerOptions != nil ? BarcodeScanner.barcodeScanner(options: barcodeScannerOptions!) : BarcodeScanner.barcodeScanner() - captureSession = AVCaptureSession() - textureId = registry?.register(self) - - // Open the camera device - device = getDefaultCameraDevice(position: cameraPosition) - - if (device == nil) { - throw MobileScannerError.noCamera - } - - device.addObserver(self, forKeyPath: #keyPath(AVCaptureDevice.torchMode), options: .new, context: nil) - device.addObserver(self, forKeyPath: #keyPath(AVCaptureDevice.videoZoomFactor), options: .new, context: nil) - - // Check the zoom factor at switching from ultra wide camera to wide camera. - standardZoomFactor = 1 - if #available(iOS 13.0, *) { - for (index, actualDevice) in device.constituentDevices.enumerated() { - if (actualDevice.deviceType != .builtInUltraWideCamera) { - if index > 0 && index <= device.virtualDeviceSwitchOverVideoZoomFactors.count { - standardZoomFactor = CGFloat(truncating: device.virtualDeviceSwitchOverVideoZoomFactors[index - 1]) - } - break - } - } - } - - do { - try device.lockForConfiguration() - if device.isFocusModeSupported(.continuousAutoFocus) { - device.focusMode = .continuousAutoFocus - } - if #available(iOS 15.4, *) , device.isFocusModeSupported(.autoFocus){ - device.automaticallyAdjustsFaceDrivenAutoFocusEnabled = false - } - device.unlockForConfiguration() - } catch {} - - captureSession!.beginConfiguration() - - // Add device input - do { - let input = try AVCaptureDeviceInput(device: device) - captureSession!.addInput(input) - } catch { - throw MobileScannerError.cameraError(error) - } - - captureSession!.sessionPreset = AVCaptureSession.Preset.photo - // Add video output. - let videoOutput = AVCaptureVideoDataOutput() - - videoOutput.videoSettings = [kCVPixelBufferPixelFormatTypeKey as String: kCVPixelFormatType_32BGRA] - videoOutput.alwaysDiscardsLateVideoFrames = true - - videoPosition = cameraPosition - // calls captureOutput() - videoOutput.setSampleBufferDelegate(self, queue: DispatchQueue.main) - - captureSession!.addOutput(videoOutput) - for connection in videoOutput.connections { - connection.videoOrientation = .portrait - if cameraPosition == .front && connection.isVideoMirroringSupported { - connection.isVideoMirrored = true - } - } - captureSession!.commitConfiguration() - - backgroundQueue.async { - guard let captureSession = self.captureSession else { - return - } - - captureSession.startRunning() - - // After the capture session started, turn on the torch (if requested) - // and reset the zoom scale back to the default. - // Ensure that these adjustments are done on the main DispatchQueue, - // as they interact with the hardware camera. - if (torch) { - DispatchQueue.main.async { - self.turnTorchOn() - } - } - - DispatchQueue.main.async { - do { - try self.resetScale() - } catch { - // If the zoom scale could not be reset, - // continue with the capture session anyway. - } - } - - if let device = self.device { - // When querying the dimensions of the camera, - // stay on the background thread, - // as this does not change the configuration of the hardware camera. - let dimensions = CMVideoFormatDescriptionGetDimensions( - device.activeFormat.formatDescription) - - completion( - MobileScannerStartParameters( - width: Double(dimensions.height), - height: Double(dimensions.width), - currentTorchState: device.hasTorch ? device.torchMode.rawValue : -1, - textureId: self.textureId ?? 0 - ) - ) - - return - } - - completion(MobileScannerStartParameters()) - } - } - - /// Stop scanning for barcodes - func stop() throws { - if (device == nil || captureSession == nil) { - throw MobileScannerError.alreadyStopped - } - - captureSession!.stopRunning() - for input in captureSession!.inputs { - captureSession!.removeInput(input) - } - for output in captureSession!.outputs { - captureSession!.removeOutput(output) - } - - latestBuffer = nil - device.removeObserver(self, forKeyPath: #keyPath(AVCaptureDevice.torchMode)) - device.removeObserver(self, forKeyPath: #keyPath(AVCaptureDevice.videoZoomFactor)) - registry?.unregisterTexture(textureId) - textureId = nil - captureSession = nil - device = nil - scanner = nil - } - - /// Toggle the torch. - /// - /// This method should be called on the main DispatchQueue. - func toggleTorch() { - guard let device = self.device else { - return - } - - if (!device.hasTorch || !device.isTorchAvailable) { - return - } - - var newTorchMode: AVCaptureDevice.TorchMode = device.torchMode - - switch(device.torchMode) { - case AVCaptureDevice.TorchMode.auto: - newTorchMode = device.isTorchActive ? AVCaptureDevice.TorchMode.off : AVCaptureDevice.TorchMode.on - break; - case AVCaptureDevice.TorchMode.off: - newTorchMode = AVCaptureDevice.TorchMode.on - break; - case AVCaptureDevice.TorchMode.on: - newTorchMode = AVCaptureDevice.TorchMode.off - break; - default: - return; - } - - if (!device.isTorchModeSupported(newTorchMode) || device.torchMode == newTorchMode) { - return; - } - - do { - try device.lockForConfiguration() - device.torchMode = newTorchMode - device.unlockForConfiguration() - } catch(_) {} - } - - /// Turn the torch on. - private func turnTorchOn() { - guard let device = self.device else { - return - } - - if (!device.hasTorch || !device.isTorchAvailable || !device.isTorchModeSupported(.on) || device.torchMode == .on) { - return - } - - do { - try device.lockForConfiguration() - device.torchMode = .on - device.unlockForConfiguration() - } catch(_) {} - } - - // Observer for torch state - public override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) { - switch keyPath { - case "torchMode": - // Off = 0, On = 1, Auto = 2 - let state = change?[.newKey] as? Int - torchModeChangeCallback(state) - case "videoZoomFactor": - let zoomFactor = change?[.newKey] as? CGFloat ?? 1 - let zoomScale = (zoomFactor - 1) / 4 - zoomScaleChangeCallback(Double(zoomScale)) - default: - break - } - } - - /// Set the zoom factor of the camera - func setScale(_ scale: CGFloat) throws { - if (device == nil) { - throw MobileScannerError.zoomWhenStopped - } - - do { - try device.lockForConfiguration() - let maxZoomFactor = device.activeFormat.videoMaxZoomFactor - - var actualScale = (scale * 4) + 1 - - // Set maximum zoomrate of 5x - actualScale = min(5.0, actualScale) - - // Limit to max rate of camera - actualScale = min(maxZoomFactor, actualScale) - - // Limit to 1.0 scale - device.videoZoomFactor = actualScale - device.unlockForConfiguration() - } catch { - throw MobileScannerError.zoomError(error) - } - - } - - /// Reset the zoom factor of the camera - func resetScale() throws { - if (device == nil) { - throw MobileScannerError.zoomWhenStopped - } - - do { - try device.lockForConfiguration() - device.videoZoomFactor = standardZoomFactor - device.unlockForConfiguration() - } catch { - throw MobileScannerError.zoomError(error) - } - } - - /// Analyze a single image - func analyzeImage(image: UIImage, position: AVCaptureDevice.Position, - barcodeScannerOptions: BarcodeScannerOptions?, callback: @escaping BarcodeScanningCallback) { - let image = VisionImage(image: image) - image.orientation = imageOrientation( - deviceOrientation: UIDevice.current.orientation, - defaultOrientation: .portrait, - position: position - ) - - let scanner: BarcodeScanner = barcodeScannerOptions != nil ? BarcodeScanner.barcodeScanner(options: barcodeScannerOptions!) : BarcodeScanner.barcodeScanner() - - scanner.process(image, completion: callback) - } - - var barcodesString: Array? - - /// Rotates images accordingly - func imageOrientation( - deviceOrientation: UIDeviceOrientation, - defaultOrientation: UIDeviceOrientation, - position: AVCaptureDevice.Position - ) -> UIImage.Orientation { - switch deviceOrientation { - case .portrait: - return position == .front ? .leftMirrored : .right - case .landscapeLeft: - return position == .front ? .downMirrored : .up - case .portraitUpsideDown: - return position == .front ? .rightMirrored : .left - case .landscapeRight: - return position == .front ? .upMirrored : .down - case .faceDown, .faceUp, .unknown: - return .up - @unknown default: - return imageOrientation(deviceOrientation: defaultOrientation, defaultOrientation: .portrait, position: .back) - } - } - - /// Sends output of OutputBuffer to a Flutter texture - public func copyPixelBuffer() -> Unmanaged? { - if latestBuffer == nil { - return nil - } - return Unmanaged.passRetained(latestBuffer) - } - - struct MobileScannerStartParameters { - var width: Double = 0.0 - var height: Double = 0.0 - var currentTorchState: Int = -1 - var textureId: Int64 = 0 - } -} - diff --git a/ios/Classes/MobileScannerError.swift b/ios/Classes/MobileScannerError.swift deleted file mode 100644 index d3fc285e6..000000000 --- a/ios/Classes/MobileScannerError.swift +++ /dev/null @@ -1,23 +0,0 @@ -// -// MobileScannerError.swift -// mobile_scanner -// -// Created by Julian Steenbakker on 24/08/2022. -// -import Foundation - -// TODO: decide if we should keep or discard this enum -// When merging the iOS / MacOS implementations we should either keep the enum or remove it - -// This enum is a bit of a leftover from older parts of the iOS implementation. -// It is used by the handler that throws these error codes, -// while the plugin class intercepts these and converts them to `FlutterError()`s. -enum MobileScannerError: Error { - case noCamera - case alreadyStarted - case alreadyStopped - case cameraError(_ error: Error) - case zoomWhenStopped - case zoomError(_ error: Error) - case analyzerError(_ error: Error) -} diff --git a/ios/Classes/MobileScannerErrorCodes.swift b/ios/Classes/MobileScannerErrorCodes.swift deleted file mode 100644 index e5eb64e6b..000000000 --- a/ios/Classes/MobileScannerErrorCodes.swift +++ /dev/null @@ -1,33 +0,0 @@ -// -// MobileScannerErrorCodes.swift -// mobile_scanner -// -// Created by Navaron Bracke on 28/05/2024. -// - -import Foundation - -/// This struct defines the error codes and error messages for MobileScanner errors. -/// -/// These are used by `FlutterError` as error code and error message. -/// -/// This struct should not be confused with `MobileScannerError`, -/// which is an implementation detail for the iOS implementation. -struct MobileScannerErrorCodes { - static let ALREADY_STARTED_ERROR = "MOBILE_SCANNER_ALREADY_STARTED_ERROR" - static let ALREADY_STARTED_ERROR_MESSAGE = "The scanner was already started." - // The error code 'BARCODE_ERROR' does not have an error message, - // because it uses the error message from the undelying error. - static let BARCODE_ERROR = "MOBILE_SCANNER_BARCODE_ERROR" - // The error code 'CAMERA_ERROR' does not have an error message, - // because it uses the error message from the underlying error. - static let CAMERA_ERROR = "MOBILE_SCANNER_CAMERA_ERROR" - static let GENERIC_ERROR = "MOBILE_SCANNER_GENERIC_ERROR" - static let GENERIC_ERROR_MESSAGE = "An unknown error occurred." - // This message is used with the 'GENERIC_ERROR' error code. - static let INVALID_ZOOM_SCALE_ERROR_MESSAGE = "The zoom scale should be between 0 and 1 (both inclusive)" - static let NO_CAMERA_ERROR = "MOBILE_SCANNER_NO_CAMERA_ERROR" - static let NO_CAMERA_ERROR_MESSAGE = "No cameras available." - static let SET_SCALE_WHEN_STOPPED_ERROR = "MOBILE_SCANNER_SET_SCALE_WHEN_STOPPED_ERROR" - static let SET_SCALE_WHEN_STOPPED_ERROR_MESSAGE = "The zoom scale cannot be changed when the camera is stopped." -} diff --git a/ios/Classes/MobileScannerPlugin.swift b/ios/Classes/MobileScannerPlugin.swift deleted file mode 100644 index ff09ff77f..000000000 --- a/ios/Classes/MobileScannerPlugin.swift +++ /dev/null @@ -1,305 +0,0 @@ -import Flutter -import MLKitVision -import MLKitBarcodeScanning -import AVFoundation -import UIKit - -public class MobileScannerPlugin: NSObject, FlutterPlugin { - - /// The mobile scanner object that handles all logic - private let mobileScanner: MobileScanner - - /// The handler sends all information via an event channel back to Flutter - private let barcodeHandler: BarcodeHandler - - /// Whether to return the input image with the barcode event. - /// This is static to avoid accessing `self` in the callback in the constructor. - private static var returnImage: Bool = false - - /// The points for the scan window. - static var scanWindow: [CGFloat]? - - private static func isBarcodeInScanWindow(barcode: Barcode, imageSize: CGSize) -> Bool { - let scanwindow = MobileScannerPlugin.scanWindow! - let barcodeminX = barcode.cornerPoints![0].cgPointValue.x - let barcodeminY = barcode.cornerPoints![1].cgPointValue.y - - let barcodewidth = barcode.cornerPoints![2].cgPointValue.x - barcodeminX - let barcodeheight = barcode.cornerPoints![3].cgPointValue.y - barcodeminY - let barcodeBox = CGRect(x: barcodeminX, y: barcodeminY, width: barcodewidth, height: barcodeheight) - - let minX = scanwindow[0] * imageSize.width - let minY = scanwindow[1] * imageSize.height - - let width = (scanwindow[2] * imageSize.width) - minX - let height = (scanwindow[3] * imageSize.height) - minY - - let scaledWindow = CGRect(x: minX, y: minY, width: width, height: height) - - return scaledWindow.contains(barcodeBox) - } - - init(barcodeHandler: BarcodeHandler, registry: FlutterTextureRegistry) { - self.mobileScanner = MobileScanner(registry: registry, mobileScannerCallback: { barcodes, error, image in - if error != nil { - barcodeHandler.publishError( - FlutterError(code: MobileScannerErrorCodes.BARCODE_ERROR, - message: error?.localizedDescription, - details: nil)) - return - } - - if barcodes == nil { - return - } - - let barcodesMap: [Any?] = barcodes!.compactMap { barcode in - if (MobileScannerPlugin.scanWindow == nil) { - return barcode.data - } - - if (MobileScannerPlugin.isBarcodeInScanWindow(barcode: barcode, imageSize: image.size)) { - return barcode.data - } - - return nil - } - - if (barcodesMap.isEmpty) { - return - } - - // The image dimensions are always provided. - // The image bytes are only non-null when `returnImage` is true. - let imageData: [String: Any?] = [ - "bytes": MobileScannerPlugin.returnImage ? FlutterStandardTypedData(bytes: image.jpegData(compressionQuality: 0.8)!) : nil, - "width": image.size.width, - "height": image.size.height, - ] - - barcodeHandler.publishEvent([ - "name": "barcode", - "data": barcodesMap, - "image": imageData, - ]) - }, torchModeChangeCallback: { torchState in - barcodeHandler.publishEvent(["name": "torchState", "data": torchState]) - }, zoomScaleChangeCallback: { zoomScale in - barcodeHandler.publishEvent(["name": "zoomScaleState", "data": zoomScale]) - }) - self.barcodeHandler = barcodeHandler - super.init() - } - - public static func register(with registrar: FlutterPluginRegistrar) { - let channel = FlutterMethodChannel(name: "dev.steenbakker.mobile_scanner/scanner/method", binaryMessenger: registrar.messenger()) - let instance = MobileScannerPlugin(barcodeHandler: BarcodeHandler(registrar: registrar), registry: registrar.textures()) - registrar.addMethodCallDelegate(instance, channel: channel) - } - - public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) { - switch call.method { - case "state": - result(mobileScanner.checkPermission()) - case "request": - AVCaptureDevice.requestAccess(for: .video, completionHandler: { result($0) }) - case "start": - start(call, result) - case "stop": - stop(result) - case "toggleTorch": - toggleTorch(result) - case "analyzeImage": - analyzeImage(call, result) - case "setScale": - setScale(call, result) - case "resetScale": - resetScale(call, result) - case "updateScanWindow": - updateScanWindow(call, result) - default: - result(FlutterMethodNotImplemented) - } - } - - /// Start the mobileScanner. - private func start(_ call: FlutterMethodCall, _ result: @escaping FlutterResult) { - let torch: Bool = (call.arguments as! Dictionary)["torch"] as? Bool ?? false - let facing: Int = (call.arguments as! Dictionary)["facing"] as? Int ?? 1 - let formats: Array = (call.arguments as! Dictionary)["formats"] as? Array ?? [] - let returnImage: Bool = (call.arguments as! Dictionary)["returnImage"] as? Bool ?? false - let speed: Int = (call.arguments as! Dictionary)["speed"] as? Int ?? 0 - let timeoutMs: Int = (call.arguments as! Dictionary)["timeout"] as? Int ?? 0 - self.mobileScanner.timeoutSeconds = Double(timeoutMs) / Double(1000) - MobileScannerPlugin.returnImage = returnImage - - let barcodeOptions: BarcodeScannerOptions? = buildBarcodeScannerOptions(formats) - - let position = facing == 0 ? AVCaptureDevice.Position.front : .back - let detectionSpeed: DetectionSpeed = DetectionSpeed(rawValue: speed)! - - do { - try mobileScanner.start(barcodeScannerOptions: barcodeOptions, cameraPosition: position, torch: torch, detectionSpeed: detectionSpeed) { parameters in - DispatchQueue.main.async { - result([ - "textureId": parameters.textureId, - "size": ["width": parameters.width, "height": parameters.height], - "currentTorchState": parameters.currentTorchState, - ]) - } - } - } catch MobileScannerError.alreadyStarted { - result(FlutterError(code: MobileScannerErrorCodes.ALREADY_STARTED_ERROR, - message: MobileScannerErrorCodes.ALREADY_STARTED_ERROR_MESSAGE, - details: nil)) - } catch MobileScannerError.noCamera { - result(FlutterError(code: MobileScannerErrorCodes.NO_CAMERA_ERROR, - message: MobileScannerErrorCodes.NO_CAMERA_ERROR_MESSAGE, - details: nil)) - } catch MobileScannerError.cameraError(let error) { - result(FlutterError(code: MobileScannerErrorCodes.CAMERA_ERROR, - message: error.localizedDescription, - details: nil)) - } catch { - result(FlutterError(code: MobileScannerErrorCodes.GENERIC_ERROR, - message: MobileScannerErrorCodes.GENERIC_ERROR_MESSAGE, - details: nil)) - } - } - - /// Stops the mobileScanner and closes the texture. - private func stop(_ result: @escaping FlutterResult) { - do { - try mobileScanner.stop() - } catch {} - result(nil) - } - - /// Toggles the torch. - private func toggleTorch(_ result: @escaping FlutterResult) { - mobileScanner.toggleTorch() - result(nil) - } - - /// Sets the zoomScale. - private func setScale(_ call: FlutterMethodCall, _ result: @escaping FlutterResult) { - let scale = call.arguments as? CGFloat - if (scale == nil) { - result(FlutterError(code: MobileScannerErrorCodes.GENERIC_ERROR, - message: MobileScannerErrorCodes.INVALID_ZOOM_SCALE_ERROR_MESSAGE, - details: "The invalid zoom scale was nil.")) - return - } - do { - try mobileScanner.setScale(scale!) - result(nil) - } catch MobileScannerError.zoomWhenStopped { - result(FlutterError(code: MobileScannerErrorCodes.SET_SCALE_WHEN_STOPPED_ERROR, - message: MobileScannerErrorCodes.SET_SCALE_WHEN_STOPPED_ERROR_MESSAGE, - details: nil)) - } catch MobileScannerError.zoomError(let error) { - result(FlutterError(code: MobileScannerErrorCodes.GENERIC_ERROR, - message: error.localizedDescription, - details: nil)) - } catch { - result(FlutterError(code: MobileScannerErrorCodes.GENERIC_ERROR, - message: MobileScannerErrorCodes.GENERIC_ERROR_MESSAGE, - details: nil)) - } - } - - /// Reset the zoomScale. - private func resetScale(_ call: FlutterMethodCall, _ result: @escaping FlutterResult) { - do { - try mobileScanner.resetScale() - result(nil) - } catch MobileScannerError.zoomWhenStopped { - result(FlutterError(code: MobileScannerErrorCodes.SET_SCALE_WHEN_STOPPED_ERROR, - message: MobileScannerErrorCodes.SET_SCALE_WHEN_STOPPED_ERROR_MESSAGE, - details: nil)) - } catch MobileScannerError.zoomError(let error) { - result(FlutterError(code: MobileScannerErrorCodes.GENERIC_ERROR, - message: error.localizedDescription, - details: nil)) - } catch { - result(FlutterError(code: MobileScannerErrorCodes.GENERIC_ERROR, - message: MobileScannerErrorCodes.GENERIC_ERROR_MESSAGE, - details: nil)) - } - } - - /// Updates the scan window rectangle. - func updateScanWindow(_ call: FlutterMethodCall, _ result: @escaping FlutterResult) { - let scanWindowData: Array? = (call.arguments as? [String: Any])?["rect"] as? [CGFloat] - MobileScannerPlugin.scanWindow = scanWindowData - - result(nil) - } - - static func arrayToRect(scanWindowData: [CGFloat]?) -> CGRect? { - if (scanWindowData == nil) { - return nil - } - - let minX = scanWindowData![0] - let minY = scanWindowData![1] - - let width = scanWindowData![2] - minX - let height = scanWindowData![3] - minY - - return CGRect(x: minX, y: minY, width: width, height: height) - } - - /// Analyzes a single image. - private func analyzeImage(_ call: FlutterMethodCall, _ result: @escaping FlutterResult) { - let formats: Array = (call.arguments as! Dictionary)["formats"] as? Array ?? [] - let scannerOptions: BarcodeScannerOptions? = buildBarcodeScannerOptions(formats) - let uiImage = UIImage(contentsOfFile: (call.arguments as! Dictionary)["filePath"] as? String ?? "") - - if (uiImage == nil) { - result(nil) - return - } - - mobileScanner.analyzeImage(image: uiImage!, position: AVCaptureDevice.Position.back, - barcodeScannerOptions: scannerOptions, callback: { barcodes, error in - if error != nil { - DispatchQueue.main.async { - result(FlutterError(code: MobileScannerErrorCodes.BARCODE_ERROR, - message: error?.localizedDescription, - details: nil)) - } - - return - } - - if (barcodes == nil || barcodes!.isEmpty) { - DispatchQueue.main.async { - result(nil) - } - - return - } - - let barcodesMap: [Any?] = barcodes!.compactMap { barcode in barcode.data } - - DispatchQueue.main.async { - result(["name": "barcode", "data": barcodesMap]) - } - }) - } - - private func buildBarcodeScannerOptions(_ formats: [Int]) -> BarcodeScannerOptions? { - guard !formats.isEmpty else { - return nil - } - - var barcodeFormats: BarcodeFormat = [] - - for format in formats { - barcodeFormats.insert(BarcodeFormat(rawValue: format)) - } - - return BarcodeScannerOptions(formats: barcodeFormats) - } -} diff --git a/ios/Classes/MobileScannerUtilities.swift b/ios/Classes/MobileScannerUtilities.swift deleted file mode 100644 index 9a18bdbe5..000000000 --- a/ios/Classes/MobileScannerUtilities.swift +++ /dev/null @@ -1,134 +0,0 @@ -import AVFoundation -import Foundation -import MLKitBarcodeScanning - -extension CVBuffer { - var image: UIImage { - let ciImage = CIImage(cvPixelBuffer: self) - let cgImage = CIContext().createCGImage(ciImage, from: ciImage.extent) - return UIImage(cgImage: cgImage!) - } -} - -extension UIDeviceOrientation { - func imageOrientation(position: AVCaptureDevice.Position) -> UIImage.Orientation { - switch self { - case .portrait: - return position == .front ? .leftMirrored : .right - case .landscapeLeft: - return position == .front ? .downMirrored : .up - case .portraitUpsideDown: - return position == .front ? .rightMirrored : .left - case .landscapeRight: - return position == .front ? .upMirrored : .down - default: - return .up - } - } -} - -extension Barcode { - var data: [String: Any?] { - return [ - "calendarEvent": calendarEvent?.data, - "contactInfo": contactInfo?.data, - "corners": cornerPoints?.map({$0.cgPointValue.data}), - "displayValue": displayValue, - "driverLicense": driverLicense?.data, - "email": email?.data, - "format": format.rawValue, - "geoPoint": geoPoint?.data, - "phone": phone?.data, - "rawBytes": rawData, - "rawValue": rawValue, - "size": frame.isNull ? nil : [ - "width": frame.width, - "height": frame.height, - ], - "sms": sms?.data, - "type": valueType.rawValue, - "url": url?.data, - "wifi": wifi?.data, - ] - } -} - -extension CGPoint { - var data: [String: Any?] { - let x1 = NSNumber(value: x.native) - let y1 = NSNumber(value: y.native) - return ["x": x1, "y": y1] - } -} - -extension BarcodeCalendarEvent { - var data: [String: Any?] { - return ["description": eventDescription, "end": end?.rawValue, "location": location, "organizer": organizer, "start": start?.rawValue, "status": status, "summary": summary] - } -} - -extension Date { - var rawValue: String { - return ISO8601DateFormatter().string(from: self) - } -} - -extension BarcodeContactInfo { - var data: [String: Any?] { - return ["addresses": addresses?.map({$0.data}), "emails": emails?.map({$0.data}), "name": name?.data, "organization": organization, "phones": phones?.map({$0.data}), "title": jobTitle, "urls": urls] - } -} - -extension BarcodeAddress { - var data: [String: Any?] { - return ["addressLines": addressLines, "type": type.rawValue] - } -} - -extension BarcodePersonName { - var data: [String: Any?] { - return ["first": first, "formattedName": formattedName, "last": last, "middle": middle, "prefix": prefix, "pronunciation": pronunciation, "suffix": suffix] - } -} - -extension BarcodeDriverLicense { - var data: [String: Any?] { - return ["addressCity": addressCity, "addressState": addressState, "addressStreet": addressStreet, "addressZip": addressZip, "birthDate": birthDate, "documentType": documentType, "expiryDate": expiryDate, "firstName": firstName, "gender": gender, "issueDate": issuingDate, "issuingCountry": issuingCountry, "lastName": lastName, "licenseNumber": licenseNumber, "middleName": middleName] - } -} - -extension BarcodeEmail { - var data: [String: Any?] { - return ["address": address, "body": body, "subject": subject, "type": type.rawValue] - } -} - -extension BarcodeGeoPoint { - var data: [String: Any?] { - return ["latitude": latitude, "longitude": longitude] - } -} - -extension BarcodePhone { - var data: [String: Any?] { - return ["number": number, "type": type.rawValue] - } -} - -extension BarcodeSMS { - var data: [String: Any?] { - return ["message": message, "phoneNumber": phoneNumber] - } -} - -extension BarcodeURLBookmark { - var data: [String: Any?] { - return ["title": title, "url": url] - } -} - -extension BarcodeWifi { - var data: [String: Any?] { - return ["encryptionType": type.rawValue, "password": password, "ssid": ssid] - } -} diff --git a/ios/mobile_scanner.podspec b/ios/mobile_scanner.podspec deleted file mode 100644 index e5da058a8..000000000 --- a/ios/mobile_scanner.podspec +++ /dev/null @@ -1,30 +0,0 @@ -# -# To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html. -# Run `pod lib lint mobile_scanner.podspec` to validate before publishing. -# -Pod::Spec.new do |s| - s.name = 'mobile_scanner' - s.version = '6.0.2' - s.summary = 'An universal scanner for Flutter based on MLKit.' - s.description = <<-DESC -An universal scanner for Flutter based on MLKit. - DESC - s.homepage = 'https://github.com/juliansteenbakker/mobile_scanner' - s.license = { :file => '../LICENSE' } - s.author = { 'Julian Steenbakker' => 'juliansteenbakker@outlook.com' } - s.source = { :path => '.' } - s.source_files = 'Classes/**/*' - s.dependency 'Flutter' - s.dependency 'GoogleMLKit/BarcodeScanning', '~> 7.0.0' - s.platform = :ios, '15.5.0' - s.static_framework = true - # Flutter.framework does not contain a i386 slice, and MLKit does not support armv7. - s.pod_target_xcconfig = { - 'DEFINES_MODULE' => 'YES', - # TODO: add back arm64 (and armv7?) when switching to the Vision API. - 'EXCLUDED_ARCHS[sdk=iphonesimulator*]' => 'i386 armv7 arm64', - 'EXCLUDED_ARCHS[sdk=iphoneos*]' => 'armv7', - } - s.swift_version = '5.0' - s.resource_bundles = { 'mobile_scanner_privacy' => ['Resources/PrivacyInfo.xcprivacy'] } -end diff --git a/macos/mobile_scanner/Sources/mobile_scanner/DetectionSpeed.swift b/macos/mobile_scanner/Sources/mobile_scanner/DetectionSpeed.swift deleted file mode 100644 index 0ae4534bd..000000000 --- a/macos/mobile_scanner/Sources/mobile_scanner/DetectionSpeed.swift +++ /dev/null @@ -1,12 +0,0 @@ -// -// DetectionSpeed.swift -// mobile_scanner -// -// Created by Julian Steenbakker on 11/11/2022. -// - -enum DetectionSpeed: Int { - case noDuplicates = 0 - case normal = 1 - case unrestricted = 2 -} diff --git a/macos/mobile_scanner/Sources/mobile_scanner/Resources/PrivacyInfo.xcprivacy b/macos/mobile_scanner/Sources/mobile_scanner/Resources/PrivacyInfo.xcprivacy deleted file mode 100644 index 6940264ae..000000000 --- a/macos/mobile_scanner/Sources/mobile_scanner/Resources/PrivacyInfo.xcprivacy +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - NSPrivacyTrackingDomains - - NSPrivacyCollectedDataTypes - - NSPrivacyTracking - - - \ No newline at end of file diff --git a/pubspec.yaml b/pubspec.yaml index 588e6e507..fe48d52df 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: mobile_scanner description: A universal barcode and QR code scanner for Flutter based on MLKit. Uses CameraX on Android, AVFoundation on iOS and Apple Vision & AVFoundation on macOS. -version: 6.0.2 +version: 7.0.0-beta.01 repository: https://github.com/juliansteenbakker/mobile_scanner screenshots: @@ -40,8 +40,10 @@ flutter: pluginClass: MobileScannerPlugin ios: pluginClass: MobileScannerPlugin + sharedDarwinSource: true macos: pluginClass: MobileScannerPlugin + sharedDarwinSource: true web: pluginClass: MobileScannerWeb fileName: src/web/mobile_scanner_web.dart