From c1abba225fadb1f8449ff00cb60dc80dd675874b Mon Sep 17 00:00:00 2001 From: Julian Steenbakker Date: Fri, 18 Feb 2022 15:23:51 +0100 Subject: [PATCH 1/2] feat: add macos support --- CHANGELOG.md | 4 + .../macos/Runner.xcodeproj/project.pbxproj | 74 ++++- .../contents.xcworkspacedata | 3 + .../macos/Runner/DebugProfile.entitlements | 2 + example/macos/Runner/Info.plist | 2 + example/macos/Runner/Release.entitlements | 2 + lib/src/mobile_scanner_controller.dart | 3 + lib/src/objects/barcode.dart | 4 +- macos/Classes/MobileScannerPlugin.swift | 306 +++++++++++++++++- macos/Classes/Util.swift | 1 + macos/mobile_scanner.podspec | 3 +- pubspec.yaml | 4 +- 12 files changed, 385 insertions(+), 23 deletions(-) create mode 100644 macos/Classes/Util.swift diff --git a/CHANGELOG.md b/CHANGELOG.md index 62dc49312..fa9cd268f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.1.0 +We now have MacOS support using Apple's Vision framework! +Keep in mind that for now, only the raw value is supported. + ## 0.0.3 * Added some API docs and README * Updated the example app diff --git a/example/macos/Runner.xcodeproj/project.pbxproj b/example/macos/Runner.xcodeproj/project.pbxproj index a5ec8bf2c..ef8685aac 100644 --- a/example/macos/Runner.xcodeproj/project.pbxproj +++ b/example/macos/Runner.xcodeproj/project.pbxproj @@ -26,6 +26,7 @@ 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F22044A3C60003C045 /* Assets.xcassets */; }; 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F42044A3C60003C045 /* MainMenu.xib */; }; 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */; }; + 5225F51353DA345E2811B6A4 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 65E614A1DF8B88C7B0CE1B97 /* Pods_Runner.framework */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -54,7 +55,7 @@ /* Begin PBXFileReference section */ 333000ED22D3DE5D00554162 /* Warnings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Warnings.xcconfig; sourceTree = ""; }; 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneratedPluginRegistrant.swift; sourceTree = ""; }; - 33CC10ED2044A3C60003C045 /* mobile_scanner_example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "mobile_scanner_example.app"; sourceTree = BUILT_PRODUCTS_DIR; }; + 33CC10ED2044A3C60003C045 /* mobile_scanner_example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = mobile_scanner_example.app; sourceTree = BUILT_PRODUCTS_DIR; }; 33CC10F02044A3C60003C045 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 33CC10F22044A3C60003C045 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = Runner/Assets.xcassets; sourceTree = ""; }; 33CC10F52044A3C60003C045 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = ""; }; @@ -66,8 +67,12 @@ 33E51913231747F40026EE4D /* DebugProfile.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = DebugProfile.entitlements; sourceTree = ""; }; 33E51914231749380026EE4D /* Release.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = Release.entitlements; sourceTree = ""; }; 33E5194F232828860026EE4D /* AppInfo.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = AppInfo.xcconfig; sourceTree = ""; }; + 65E614A1DF8B88C7B0CE1B97 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = ""; }; 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = ""; }; + CB0901144E09E7D7CA20584F /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; + D522F9F6F348C5944077606B /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; + F63009B5E287A1C82F9D7D2F /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -75,12 +80,23 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 5225F51353DA345E2811B6A4 /* Pods_Runner.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 20F8C9AA20C2A495C125E194 /* Pods */ = { + isa = PBXGroup; + children = ( + CB0901144E09E7D7CA20584F /* Pods-Runner.debug.xcconfig */, + D522F9F6F348C5944077606B /* Pods-Runner.release.xcconfig */, + F63009B5E287A1C82F9D7D2F /* Pods-Runner.profile.xcconfig */, + ); + path = Pods; + sourceTree = ""; + }; 33BA886A226E78AF003329D5 /* Configs */ = { isa = PBXGroup; children = ( @@ -98,7 +114,8 @@ 33FAB671232836740065AC1E /* Runner */, 33CEB47122A05771004F2AC0 /* Flutter */, 33CC10EE2044A3C60003C045 /* Products */, - D73912EC22F37F3D000D13A0 /* Frameworks */, + 20F8C9AA20C2A495C125E194 /* Pods */, + 3539353E79638640B4999C09 /* Frameworks */, ); sourceTree = ""; }; @@ -145,9 +162,10 @@ path = Runner; sourceTree = ""; }; - D73912EC22F37F3D000D13A0 /* Frameworks */ = { + 3539353E79638640B4999C09 /* Frameworks */ = { isa = PBXGroup; children = ( + 65E614A1DF8B88C7B0CE1B97 /* Pods_Runner.framework */, ); name = Frameworks; sourceTree = ""; @@ -159,11 +177,13 @@ isa = PBXNativeTarget; buildConfigurationList = 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */; buildPhases = ( + 696298230BDAD783AEC51C81 /* [CP] Check Pods Manifest.lock */, 33CC10E92044A3C60003C045 /* Sources */, 33CC10EA2044A3C60003C045 /* Frameworks */, 33CC10EB2044A3C60003C045 /* Resources */, 33CC110E2044A8840003C045 /* Bundle Framework */, 3399D490228B24CF009A79C7 /* ShellScript */, + 8A90D2BC4083C5ACCEEBF32B /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); @@ -270,6 +290,45 @@ shellPath = /bin/sh; shellScript = "\"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh && touch Flutter/ephemeral/tripwire"; }; + 696298230BDAD783AEC51C81 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + 8A90D2BC4083C5ACCEEBF32B /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ @@ -344,7 +403,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.11; + MACOSX_DEPLOYMENT_TARGET = 10.13; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = macosx; SWIFT_COMPILATION_MODE = wholemodule; @@ -366,6 +425,7 @@ "$(inherited)", "@executable_path/../Frameworks", ); + MACOSX_DEPLOYMENT_TARGET = 10.13; PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_VERSION = 5.0; }; @@ -423,7 +483,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.11; + MACOSX_DEPLOYMENT_TARGET = 10.13; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = macosx; @@ -470,7 +530,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.11; + MACOSX_DEPLOYMENT_TARGET = 10.13; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = macosx; SWIFT_COMPILATION_MODE = wholemodule; @@ -492,6 +552,7 @@ "$(inherited)", "@executable_path/../Frameworks", ); + MACOSX_DEPLOYMENT_TARGET = 10.13; PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 5.0; @@ -512,6 +573,7 @@ "$(inherited)", "@executable_path/../Frameworks", ); + MACOSX_DEPLOYMENT_TARGET = 10.13; PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_VERSION = 5.0; }; diff --git a/example/macos/Runner.xcworkspace/contents.xcworkspacedata b/example/macos/Runner.xcworkspace/contents.xcworkspacedata index 1d526a16e..21a3cc14c 100644 --- a/example/macos/Runner.xcworkspace/contents.xcworkspacedata +++ b/example/macos/Runner.xcworkspace/contents.xcworkspacedata @@ -4,4 +4,7 @@ + + diff --git a/example/macos/Runner/DebugProfile.entitlements b/example/macos/Runner/DebugProfile.entitlements index dddb8a30c..0830a9c55 100644 --- a/example/macos/Runner/DebugProfile.entitlements +++ b/example/macos/Runner/DebugProfile.entitlements @@ -6,6 +6,8 @@ com.apple.security.cs.allow-jit + com.apple.security.device.camera + com.apple.security.network.server diff --git a/example/macos/Runner/Info.plist b/example/macos/Runner/Info.plist index 4789daa6a..1c0f3d7d4 100644 --- a/example/macos/Runner/Info.plist +++ b/example/macos/Runner/Info.plist @@ -2,6 +2,8 @@ + NSCameraUsageDescription + The camera is required to scan barcodes or QR codes CFBundleDevelopmentRegion $(DEVELOPMENT_LANGUAGE) CFBundleExecutable diff --git a/example/macos/Runner/Release.entitlements b/example/macos/Runner/Release.entitlements index 852fa1a47..eaadb89c0 100644 --- a/example/macos/Runner/Release.entitlements +++ b/example/macos/Runner/Release.entitlements @@ -4,5 +4,7 @@ com.apple.security.app-sandbox + com.apple.security.device.camera + diff --git a/lib/src/mobile_scanner_controller.dart b/lib/src/mobile_scanner_controller.dart index 01ba23a18..776c96efd 100644 --- a/lib/src/mobile_scanner_controller.dart +++ b/lib/src/mobile_scanner_controller.dart @@ -86,6 +86,9 @@ class MobileScannerController { final barcode = Barcode.fromNative(data); barcodesController.add(barcode); break; + case 'barcodeMac': + barcodesController.add(Barcode(rawValue: data['payload'])); + break; default: throw UnimplementedError(); } diff --git a/lib/src/objects/barcode.dart b/lib/src/objects/barcode.dart index 8b9430084..a99c51e63 100644 --- a/lib/src/objects/barcode.dart +++ b/lib/src/objects/barcode.dart @@ -18,7 +18,7 @@ class Barcode { /// Returns raw bytes as it was encoded in the barcode. /// /// Returns null if the raw bytes can not be determined. - final Uint8List rawBytes; + final Uint8List? rawBytes; /// Returns barcode value as it was encoded in the barcode. Structured values are not parsed, for example: 'MEBKM:TITLE:Google;URL://www.google.com;;'. /// @@ -63,6 +63,8 @@ class Barcode { /// Gets parsed WiFi AP details. final WiFi? wifi; + Barcode({this.corners, this.format = BarcodeFormat.ean13, this.rawBytes, this.type = BarcodeType.text, this.calendarEvent, this.contactInfo, this.driverLicense, this.email, this.geoPoint, this.phone, this.sms, this.url, this.wifi, required this.rawValue}); + /// Create a [Barcode] from native data. Barcode.fromNative(Map data) : corners = toCorners(data['corners']), diff --git a/macos/Classes/MobileScannerPlugin.swift b/macos/Classes/MobileScannerPlugin.swift index 40b668988..7cb7f6653 100644 --- a/macos/Classes/MobileScannerPlugin.swift +++ b/macos/Classes/MobileScannerPlugin.swift @@ -1,19 +1,299 @@ -import Cocoa +import AVFoundation import FlutterMacOS +import Vision -public class MobileScannerPlugin: NSObject, FlutterPlugin { - public static func register(with registrar: FlutterPluginRegistrar) { - let channel = FlutterMethodChannel(name: "mobile_scanner", binaryMessenger: registrar.messenger) - let instance = MobileScannerPlugin() - registrar.addMethodCallDelegate(instance, channel: channel) - } +public class MobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHandler, FlutterTexture, AVCaptureVideoDataOutputSampleBufferDelegate { + + let registry: FlutterTextureRegistry + + // Sink for publishing event changes + var sink: FlutterEventSink! + + // Texture id of the camera preview + var textureId: Int64! + + // Capture session of the camera + var captureSession: AVCaptureSession! + + // The selected camera + var device: AVCaptureDevice! + + // Image to be sent to the texture + var latestBuffer: CVImageBuffer! + + + var analyzeMode: Int = 0 + var analyzing: Bool = false + var position = AVCaptureDevice.Position.back + + public static func register(with registrar: FlutterPluginRegistrar) { + let instance = MobileScannerPlugin(registrar.textures) + + let method = FlutterMethodChannel(name: + "dev.steenbakker.mobile_scanner/scanner/method", binaryMessenger: registrar.messenger) + let event = FlutterEventChannel(name: + "dev.steenbakker.mobile_scanner/scanner/event", binaryMessenger: registrar.messenger) + registrar.addMethodCallDelegate(instance, channel: method) + event.setStreamHandler(instance) + } + + init(_ registry: FlutterTextureRegistry) { + self.registry = registry + super.init() + } + + + public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) { + switch call.method { + case "state": + checkPermission(call, result) + case "request": + requestPermission(call, result) + case "start": + start(call, result) + case "torch": + switchTorch(call, result) + case "analyze": + switchAnalyzeMode(call, result) + case "stop": + stop(result) + default: + result(FlutterMethodNotImplemented) + } + } + + // FlutterStreamHandler + public func onListen(withArguments arguments: Any?, eventSink events: @escaping FlutterEventSink) -> FlutterError? { + sink = events + return nil + } + + // FlutterStreamHandler + public func onCancel(withArguments arguments: Any?) -> FlutterError? { + sink = nil + return nil + } + + // FlutterTexture + public func copyPixelBuffer() -> Unmanaged? { + if latestBuffer == nil { + return nil + } + return Unmanaged.passRetained(latestBuffer) + } + + var i = 0 + + + // Gets called when a new image is added to the buffer + public func captureOutput(_ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) { + i = i + 1; + + latestBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) + registry.textureFrameAvailable(textureId) + + switch analyzeMode { + case 1: // barcode + + // Limit the analyzer because the texture output will freeze otherwise + if i / 10 == 1 { + i = 0 + } else { + break + } + let imageRequestHandler = VNImageRequestHandler( + cvPixelBuffer: latestBuffer, + orientation: .right) + + do { + try imageRequestHandler.perform([VNDetectBarcodesRequest { (request, error) in + if error == nil { + if let results = request.results as? [VNBarcodeObservation] { + for barcode in results { + let barcodeType = String(barcode.symbology.rawValue).replacingOccurrences(of: "VNBarcodeSymbology", with: "") + let event: [String: Any?] = ["name": "barcodeMac", "data" : ["payload": barcode.payloadStringValue, "symbology": barcodeType]] + self.sink?(event) + + // if barcodeType == "QR" { + // let image = CIImage(image: source) + // image?.cropping(to: barcode.boundingBox) + // self.qrCodeDescriptor(qrCode: barcode, qrCodeImage: image!) + // } + } + } + } else { + print(error!.localizedDescription) + } + }]) + } catch { + print(error) + } + + default: // none + break + } + } + + func checkPermission(_ call: FlutterMethodCall, _ result: @escaping FlutterResult) { + if #available(macOS 10.14, *) { + let status = AVCaptureDevice.authorizationStatus(for: .video) + switch status { + case .notDetermined: + result(0) + case .authorized: + result(1) + default: + result(2) + } + } else { + result(1) + } + } + + func requestPermission(_ call: FlutterMethodCall, _ result: @escaping FlutterResult) { + if #available(macOS 10.14, *) { + AVCaptureDevice.requestAccess(for: .video, completionHandler: { result($0) }) + } else { + result(0) + } + } + + func start(_ call: FlutterMethodCall, _ result: @escaping FlutterResult) { + textureId = registry.register(self) + captureSession = AVCaptureSession() + + let argReader = MapArgumentReader(call.arguments as? [String: Any]) + +// let ratio: Int = argReader.int(key: "ratio") + let torch: Bool = argReader.bool(key: "torch") ?? false + let facing: Int = argReader.int(key: "facing") ?? 1 - public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) { - switch call.method { - case "getPlatformVersion": - result("macOS " + ProcessInfo.processInfo.operatingSystemVersionString) - default: - result(FlutterMethodNotImplemented) + // Set the camera to use + position = facing == 0 ? AVCaptureDevice.Position.front : .back + + // Open the camera device + if #available(macOS 10.15, *) { + device = AVCaptureDevice.DiscoverySession(deviceTypes: [.builtInWideAngleCamera], mediaType: .video, position: position).devices.first + } else { + device = AVCaptureDevice.devices(for: .video).filter({$0.position == position}).first + } + + // Enable the torch if parameter is set and torch is available + if (device.hasTorch) { + do { + try device.lockForConfiguration() + device.torchMode = torch ? .on : .off + device.unlockForConfiguration() + } catch { + result(FlutterError(code: error.localizedDescription, message: nil, details: nil)) + } + } + + device.addObserver(self, forKeyPath: #keyPath(AVCaptureDevice.torchMode), options: .new, context: nil) + captureSession.beginConfiguration() + + // Add device input + do { + let input = try AVCaptureDeviceInput(device: device) + captureSession.addInput(input) + } catch { + result(FlutterError(code: error.localizedDescription, message: nil, details: nil)) + } + captureSession.sessionPreset = AVCaptureSession.Preset.photo; + // 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 { +// connection.videoOrientation = .portrait + 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, "torchable": device.hasTorch] + result(answer) + } + + func switchTorch(_ call: FlutterMethodCall, _ result: @escaping FlutterResult) { + do { + try device.lockForConfiguration() + device.torchMode = call.arguments as! Int == 1 ? .on : .off + device.unlockForConfiguration() + result(nil) + } catch { + result(FlutterError(code: error.localizedDescription, message: nil, details: nil)) + } + } + + func switchAnalyzeMode(_ call: FlutterMethodCall, _ result: @escaping FlutterResult) { + analyzeMode = call.arguments as! Int + result(nil) } + + func stop(_ result: FlutterResult) { + captureSession.stopRunning() + for input in captureSession.inputs { + captureSession.removeInput(input) + } + for output in captureSession.outputs { + captureSession.removeOutput(output) + } + device.removeObserver(self, forKeyPath: #keyPath(AVCaptureDevice.torchMode)) + registry.unregisterTexture(textureId) + + analyzeMode = 0 + latestBuffer = nil + captureSession = nil + device = nil + textureId = nil + + result(nil) + } + + // 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 + let event: [String: Any?] = ["name": "torchState", "data": state] + sink?(event) + default: + break + } + } +} + +class MapArgumentReader { + + let args: [String: Any]? + + init(_ args: [String: Any]?) { + self.args = args + } + + func string(key: String) -> String? { + return args?[key] as? String + } + + func int(key: String) -> Int? { + return (args?[key] as? NSNumber)?.intValue + } + + func bool(key: String) -> Bool? { + return (args?[key] as? NSNumber)?.boolValue + } + + func stringArray(key: String) -> [String]? { + return args?[key] as? [String] } + } diff --git a/macos/Classes/Util.swift b/macos/Classes/Util.swift new file mode 100644 index 000000000..8b1378917 --- /dev/null +++ b/macos/Classes/Util.swift @@ -0,0 +1 @@ + diff --git a/macos/mobile_scanner.podspec b/macos/mobile_scanner.podspec index e5a2d72ec..8e0414211 100644 --- a/macos/mobile_scanner.podspec +++ b/macos/mobile_scanner.podspec @@ -15,8 +15,7 @@ An universal scanner for Flutter based on MLKit. s.source = { :path => '.' } s.source_files = 'Classes/**/*' s.dependency 'FlutterMacOS' - - s.platform = :osx, '10.11' + s.platform = :osx, '10.13' s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES' } s.swift_version = '5.0' end diff --git a/pubspec.yaml b/pubspec.yaml index 53cc73bc4..615fd2067 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: mobile_scanner description: A universal scanner for Flutter based on MLKit. Uses CameraX on Android and AVFoundation on iOS. -version: 0.0.3 +version: 0.1.0 repository: https://github.com/juliansteenbakker/mobile_scanner environment: @@ -24,3 +24,5 @@ flutter: pluginClass: MobileScannerPlugin ios: pluginClass: MobileScannerPlugin + macos: + pluginClass: MobileScannerPlugin \ No newline at end of file From c44e56efc96b3727d9eaabd122b8e99ac7600e15 Mon Sep 17 00:00:00 2001 From: Julian Steenbakker Date: Mon, 21 Feb 2022 09:24:16 +0100 Subject: [PATCH 2/2] style: flutter format --- lib/src/objects/barcode.dart | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/lib/src/objects/barcode.dart b/lib/src/objects/barcode.dart index a99c51e63..1125e5a7e 100644 --- a/lib/src/objects/barcode.dart +++ b/lib/src/objects/barcode.dart @@ -63,7 +63,21 @@ class Barcode { /// Gets parsed WiFi AP details. final WiFi? wifi; - Barcode({this.corners, this.format = BarcodeFormat.ean13, this.rawBytes, this.type = BarcodeType.text, this.calendarEvent, this.contactInfo, this.driverLicense, this.email, this.geoPoint, this.phone, this.sms, this.url, this.wifi, required this.rawValue}); + Barcode( + {this.corners, + this.format = BarcodeFormat.ean13, + this.rawBytes, + this.type = BarcodeType.text, + this.calendarEvent, + this.contactInfo, + this.driverLicense, + this.email, + this.geoPoint, + this.phone, + this.sms, + this.url, + this.wifi, + required this.rawValue}); /// Create a [Barcode] from native data. Barcode.fromNative(Map data)