Moved the size listener to plugin side.flutter-beta
| @@ -17,7 +17,7 @@ import io.flutter.plugin.common.BinaryMessenger | |||
| import io.flutter.plugin.common.MethodCall | |||
| import io.flutter.plugin.common.MethodChannel | |||
| import io.flutter.plugin.platform.PlatformView | |||
| class QRView(messenger: BinaryMessenger, id: Int, private val context: Context) : | |||
| class QRView(messenger: BinaryMessenger, id: Int, private val context: Context, private val params: HashMap<String, Any>) : | |||
| PlatformView, MethodChannel.MethodCallHandler { | |||
| private var isTorchOn: Boolean = false | |||
| @@ -66,6 +66,12 @@ class QRView(messenger: BinaryMessenger, id: Int, private val context: Context) | |||
| override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) { | |||
| when(call.method){ | |||
| "startScan" -> { | |||
| startScan() | |||
| } | |||
| "stopScan" -> { | |||
| stopScan() | |||
| } | |||
| "flipCamera" -> { | |||
| flipCamera() | |||
| } | |||
| @@ -127,14 +133,16 @@ class QRView(messenger: BinaryMessenger, id: Int, private val context: Context) | |||
| private fun initBarCodeView(): BarcodeView? { | |||
| if (barcodeView == null) { | |||
| barcodeView = createBarCodeView() | |||
| barcodeView = BarcodeView(Shared.activity) | |||
| if (params["cameraFacing"] as Int == 1) { | |||
| barcodeView?.cameraSettings?.requestedCameraId = CameraInfo.CAMERA_FACING_FRONT | |||
| } | |||
| } | |||
| return barcodeView | |||
| } | |||
| private fun createBarCodeView(): BarcodeView { | |||
| val barcode = BarcodeView(Shared.activity) | |||
| barcode.decodeContinuous( | |||
| private fun startScan() { | |||
| barcodeView?.decodeContinuous( | |||
| object : BarcodeCallback { | |||
| override fun barcodeResult(result: BarcodeResult) { | |||
| val code = mapOf( | |||
| @@ -147,7 +155,10 @@ class QRView(messenger: BinaryMessenger, id: Int, private val context: Context) | |||
| override fun possibleResultPoints(resultPoints: List<ResultPoint>) {} | |||
| } | |||
| ) | |||
| return barcode | |||
| } | |||
| private fun stopScan() { | |||
| barcodeView?.stopDecoding() | |||
| } | |||
| private fun hasCameraPermission(): Boolean { | |||
| @@ -10,8 +10,9 @@ import io.flutter.plugin.platform.PlatformViewFactory | |||
| class QRViewFactory(private val messenger: BinaryMessenger) : | |||
| PlatformViewFactory(StandardMessageCodec.INSTANCE) { | |||
| override fun create(context: Context, id: Int, obj: Any?): PlatformView { | |||
| return QRView(messenger, id, context) | |||
| override fun create(context: Context, id: Int, args: Any?): PlatformView { | |||
| val params = args as HashMap<String, Any> | |||
| return QRView(messenger, id, context, params) | |||
| } | |||
| } | |||
| @@ -9,6 +9,7 @@ | |||
| .buildlog/ | |||
| .history | |||
| .svn/ | |||
| .last_build_id | |||
| # IntelliJ related | |||
| *.iml | |||
| @@ -264,13 +264,11 @@ | |||
| ); | |||
| inputPaths = ( | |||
| "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh", | |||
| "${PODS_ROOT}/../Flutter/Flutter.framework", | |||
| "${BUILT_PRODUCTS_DIR}/MTBBarcodeScanner/MTBBarcodeScanner.framework", | |||
| "${BUILT_PRODUCTS_DIR}/qr_code_scanner/qr_code_scanner.framework", | |||
| ); | |||
| name = "[CP] Embed Pods Frameworks"; | |||
| outputPaths = ( | |||
| "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Flutter.framework", | |||
| "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/MTBBarcodeScanner.framework", | |||
| "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/qr_code_scanner.framework", | |||
| ); | |||
| @@ -154,25 +154,18 @@ class _QRViewExampleState extends State<QRViewExample> { | |||
| : 300.0; | |||
| // To ensure the Scanner view is properly sizes after rotation | |||
| // we need to listen for Flutter SizeChanged notification and update controller | |||
| return NotificationListener<SizeChangedLayoutNotification>( | |||
| onNotification: (notification) { | |||
| Future.microtask( | |||
| () => controller?.updateDimensions(qrKey, scanArea: scanArea)); | |||
| return false; | |||
| }, | |||
| child: SizeChangedLayoutNotifier( | |||
| key: const Key('qr-size-notifier'), | |||
| child: QRView( | |||
| key: qrKey, | |||
| onQRViewCreated: _onQRViewCreated, | |||
| overlay: QrScannerOverlayShape( | |||
| borderColor: Colors.red, | |||
| borderRadius: 10, | |||
| borderLength: 30, | |||
| borderWidth: 10, | |||
| cutOutSize: scanArea, | |||
| ), | |||
| ))); | |||
| return QRView( | |||
| key: qrKey, | |||
| cameraFacing: CameraFacing.front, | |||
| onQRViewCreated: _onQRViewCreated, | |||
| overlay: QrScannerOverlayShape( | |||
| borderColor: Colors.red, | |||
| borderRadius: 10, | |||
| borderLength: 30, | |||
| borderWidth: 10, | |||
| cutOutSize: scanArea, | |||
| ), | |||
| ); | |||
| } | |||
| void _onQRViewCreated(QRViewController controller) { | |||
| @@ -13,67 +13,28 @@ public class QRView:NSObject,FlutterPlatformView { | |||
| var scanner: MTBBarcodeScanner? | |||
| var registrar: FlutterPluginRegistrar | |||
| var channel: FlutterMethodChannel | |||
| var cameraFacing: MTBCamera | |||
| public init(withFrame frame: CGRect, withRegistrar registrar: FlutterPluginRegistrar, withId id: Int64){ | |||
| public init(withFrame frame: CGRect, withRegistrar registrar: FlutterPluginRegistrar, withId id: Int64, params: Dictionary<String, Any>){ | |||
| self.registrar = registrar | |||
| previewView = UIView(frame: frame) | |||
| cameraFacing = MTBCamera.init(rawValue: UInt(Int(params["cameraFacing"] as! Double))) ?? MTBCamera.back | |||
| channel = FlutterMethodChannel(name: "net.touchcapture.qr.flutterqr/qrview_\(id)", binaryMessenger: registrar.messenger()) | |||
| } | |||
| func isCameraAvailable(success: Bool) -> Void { | |||
| if success { | |||
| do { | |||
| try scanner?.startScanning(resultBlock: { [weak self] codes in | |||
| if let codes = codes { | |||
| for code in codes { | |||
| var typeString: String; | |||
| switch(code.type) { | |||
| case AVMetadataObject.ObjectType.aztec: | |||
| typeString = "AZTEC" | |||
| case AVMetadataObject.ObjectType.code39: | |||
| typeString = "CODE_39" | |||
| case AVMetadataObject.ObjectType.code93: | |||
| typeString = "CODE_93" | |||
| case AVMetadataObject.ObjectType.code128: | |||
| typeString = "CODE_128" | |||
| case AVMetadataObject.ObjectType.dataMatrix: | |||
| typeString = "DATA_MATRIX" | |||
| case AVMetadataObject.ObjectType.ean8: | |||
| typeString = "EAN_8" | |||
| case AVMetadataObject.ObjectType.ean13: | |||
| typeString = "EAN_13" | |||
| case AVMetadataObject.ObjectType.itf14: | |||
| typeString = "ITF" | |||
| case AVMetadataObject.ObjectType.pdf417: | |||
| typeString = "PDF_417" | |||
| case AVMetadataObject.ObjectType.qr: | |||
| typeString = "QR_CODE" | |||
| case AVMetadataObject.ObjectType.upce: | |||
| typeString = "UPC_E" | |||
| default: | |||
| return | |||
| } | |||
| guard let stringValue = code.stringValue else { continue } | |||
| let result = ["code": stringValue, "type": typeString] | |||
| self?.channel.invokeMethod("onRecognizeQR", arguments: result) | |||
| } | |||
| } | |||
| }) | |||
| } catch { | |||
| NSLog("Unable to start scanning") | |||
| } | |||
| } else { | |||
| UIAlertView(title: "Scanning Unavailable", message: "This app does not have permission to access the camera", delegate: nil, cancelButtonTitle: nil, otherButtonTitles: "Ok").show() | |||
| } | |||
| deinit { | |||
| scanner?.stopScanning() | |||
| } | |||
| public func view() -> UIView { | |||
| channel.setMethodCallHandler({ | |||
| [weak self] (call: FlutterMethodCall, result: FlutterResult) -> Void in | |||
| [weak self] (call: FlutterMethodCall, result: @escaping FlutterResult) -> Void in | |||
| switch(call.method){ | |||
| case "setDimensions": | |||
| let arguments = call.arguments as! Dictionary<String, Double> | |||
| self?.setDimensions(width: arguments["width"] ?? 0, height: arguments["height"] ?? 0, scanArea: arguments["scanArea"] ?? 0) | |||
| case "startScan": | |||
| self?.startScan(result) | |||
| case "flipCamera": | |||
| self?.flipCamera() | |||
| case "toggleFlash": | |||
| @@ -106,9 +67,67 @@ public class QRView:NSObject,FlutterPlatformView { | |||
| self.scanner?.scanRect = CGRect(x: Double(midX) - (scanArea / 2), y: Double(midY) - (scanArea / 2), width: scanArea, height: scanArea) | |||
| } | |||
| } | |||
| MTBBarcodeScanner.requestCameraPermission(success: isCameraAvailable) | |||
| } | |||
| } | |||
| func startScan(_ result: @escaping FlutterResult) -> Void { | |||
| scanner = MTBBarcodeScanner(previewView: previewView) | |||
| MTBBarcodeScanner.requestCameraPermission(success: { permissionGranted in | |||
| if permissionGranted { | |||
| do { | |||
| try self.scanner?.startScanning(with: self.cameraFacing, resultBlock: { [weak self] codes in | |||
| if let codes = codes { | |||
| for code in codes { | |||
| var typeString: String; | |||
| switch(code.type) { | |||
| case AVMetadataObject.ObjectType.aztec: | |||
| typeString = "AZTEC" | |||
| case AVMetadataObject.ObjectType.code39: | |||
| typeString = "CODE_39" | |||
| case AVMetadataObject.ObjectType.code93: | |||
| typeString = "CODE_93" | |||
| case AVMetadataObject.ObjectType.code128: | |||
| typeString = "CODE_128" | |||
| case AVMetadataObject.ObjectType.dataMatrix: | |||
| typeString = "DATA_MATRIX" | |||
| case AVMetadataObject.ObjectType.ean8: | |||
| typeString = "EAN_8" | |||
| case AVMetadataObject.ObjectType.ean13: | |||
| typeString = "EAN_13" | |||
| case AVMetadataObject.ObjectType.itf14: | |||
| typeString = "ITF" | |||
| case AVMetadataObject.ObjectType.pdf417: | |||
| typeString = "PDF_417" | |||
| case AVMetadataObject.ObjectType.qr: | |||
| typeString = "QR_CODE" | |||
| case AVMetadataObject.ObjectType.upce: | |||
| typeString = "UPC_E" | |||
| default: | |||
| return | |||
| } | |||
| guard let stringValue = code.stringValue else { continue } | |||
| let result = ["code": stringValue, "type": typeString] | |||
| self?.channel.invokeMethod("onRecognizeQR", arguments: result) | |||
| } | |||
| } | |||
| }) | |||
| } catch { | |||
| let error = FlutterError(code: "unknown-error", message: "Unable to start scanning", details: nil) | |||
| result(error) | |||
| } | |||
| } else { | |||
| let error = FlutterError(code: "cameraPermission", message: "Permission denied to access the camera", details: nil) | |||
| result(error) | |||
| } | |||
| }) | |||
| } | |||
| func stopScan(){ | |||
| if let sc: MTBBarcodeScanner = scanner { | |||
| if sc.isScanning() { | |||
| sc.stopScanning() | |||
| } | |||
| } | |||
| } | |||
| @@ -17,8 +17,8 @@ public class QRViewFactory: NSObject, FlutterPlatformViewFactory { | |||
| } | |||
| public func create(withFrame frame: CGRect, viewIdentifier viewId: Int64, arguments args: Any?) -> FlutterPlatformView { | |||
| let dictionary = args as! Dictionary<String, Double> | |||
| return QRView(withFrame: CGRect(x: 0, y: 0, width: dictionary["width"] ?? 0, height: dictionary["height"] ?? 0), withRegistrar: registrar!,withId: viewId) | |||
| let params = args as! Dictionary<String, Double> | |||
| return QRView(withFrame: frame, withRegistrar: registrar!,withId: viewId, params: params) | |||
| } | |||
| public func createArgsCodec() -> FlutterMessageCodec & NSObjectProtocol { | |||
| @@ -7,6 +7,14 @@ import 'package:qr_code_scanner/qr_code_scanner.dart'; | |||
| typedef QRViewCreatedCallback = void Function(QRViewController); | |||
| enum CameraFacing { | |||
| /// Shows back facing camera. | |||
| back, | |||
| /// Shows front facing camera. | |||
| front | |||
| } | |||
| enum BarcodeFormat { | |||
| /// Aztec 2D barcode format. | |||
| aztec, | |||
| @@ -80,6 +88,11 @@ const _formatNames = <String, BarcodeFormat>{ | |||
| 'UPC_EAN_EXTENSION': BarcodeFormat.upcEanExtension, | |||
| }; | |||
| /// The [Barcode] object holds information about the barcode or qr code. | |||
| /// | |||
| /// [code] is the content of the barcode. | |||
| /// [format] displays which type the code is. | |||
| /// Only for Android, [rawBytes] gives a list of bytes of the result. | |||
| class Barcode { | |||
| Barcode(this.code, this.format, this.rawBytes); | |||
| @@ -90,60 +103,84 @@ class Barcode { | |||
| final List<int> rawBytes; | |||
| } | |||
| /// The [QRView] is the view where the camera and the barcode scanner gets displayed. | |||
| class QRView extends StatefulWidget { | |||
| const QRView({ | |||
| @required Key key, | |||
| @required this.onQRViewCreated, | |||
| this.overlay, | |||
| this.overlayMargin = EdgeInsets.zero, | |||
| this.cameraFacing = CameraFacing.back, | |||
| }) : assert(key != null), | |||
| assert(onQRViewCreated != null), | |||
| super(key: key); | |||
| final QRViewCreatedCallback onQRViewCreated; | |||
| final ShapeBorder overlay; | |||
| final EdgeInsetsGeometry overlayMargin; | |||
| final CameraFacing cameraFacing; | |||
| @override | |||
| State<StatefulWidget> createState() => _QRViewState(); | |||
| } | |||
| class _QRViewState extends State<QRView> { | |||
| var _channel; | |||
| @override | |||
| Widget build(BuildContext context) { | |||
| return NotificationListener( | |||
| onNotification: onNotification, | |||
| child: SizeChangedLayoutNotifier( | |||
| child: (widget.overlay != null) ? | |||
| _getPlatformQrViewWithOverlay() : | |||
| _getPlatformQrView(), | |||
| ), | |||
| ); | |||
| } | |||
| bool onNotification(notification) { | |||
| Future.microtask(() => { | |||
| QRViewController.updateDimensions( | |||
| widget.key, | |||
| _channel, | |||
| scanArea: widget.overlay != null ? | |||
| (widget.overlay as QrScannerOverlayShape).cutOutSize : 0.0) | |||
| }); | |||
| return false; | |||
| } | |||
| Widget _getPlatformQrViewWithOverlay() { | |||
| return Stack( | |||
| children: [ | |||
| _getPlatformQrView(widget.key), | |||
| if (widget.overlay != null) | |||
| Container( | |||
| padding: widget.overlayMargin, | |||
| decoration: ShapeDecoration( | |||
| shape: widget.overlay, | |||
| ), | |||
| ) | |||
| else | |||
| Container(), | |||
| _getPlatformQrView(), | |||
| Container( | |||
| padding: widget.overlayMargin, | |||
| decoration: ShapeDecoration( | |||
| shape: widget.overlay, | |||
| ), | |||
| ) | |||
| ], | |||
| ); | |||
| } | |||
| Widget _getPlatformQrView(GlobalKey key) { | |||
| Widget _getPlatformQrView() { | |||
| Widget _platformQrView; | |||
| switch (defaultTargetPlatform) { | |||
| case TargetPlatform.android: | |||
| _platformQrView = AndroidView( | |||
| viewType: 'net.touchcapture.qr.flutterqr/qrview', | |||
| onPlatformViewCreated: _onPlatformViewCreated, | |||
| creationParams: _QrCameraSettings(cameraFacing: widget.cameraFacing).toMap(), | |||
| creationParamsCodec: StandardMessageCodec(), | |||
| ); | |||
| break; | |||
| case TargetPlatform.iOS: | |||
| _platformQrView = UiKitView( | |||
| viewType: 'net.touchcapture.qr.flutterqr/qrview', | |||
| onPlatformViewCreated: _onPlatformViewCreated, | |||
| creationParams: | |||
| _CreationParams.fromWidget(MediaQuery.of(context).size.width, 400) | |||
| .toMap(), | |||
| creationParams: _QrCameraSettings(cameraFacing: widget.cameraFacing).toMap(), | |||
| creationParamsCodec: StandardMessageCodec(), | |||
| ); | |||
| break; | |||
| @@ -155,101 +192,109 @@ class _QRViewState extends State<QRView> { | |||
| } | |||
| void _onPlatformViewCreated(int id) { | |||
| if (widget.onQRViewCreated == null) { | |||
| return; | |||
| } | |||
| // We pass the cutout size so that the scanner respects the scan area. | |||
| var cutOutSize = 0.0; | |||
| if (widget.overlay != null) { | |||
| cutOutSize = (widget.overlay as QrScannerOverlayShape).cutOutSize; | |||
| } | |||
| widget.onQRViewCreated(QRViewController._(id, widget.key, cutOutSize)); | |||
| } | |||
| } | |||
| _channel = MethodChannel('net.touchcapture.qr.flutterqr/qrview_$id'); | |||
| class _CreationParams { | |||
| _CreationParams({this.width, this.height}); | |||
| // Start scan after creation of the view | |||
| final controller = QRViewController._(_channel, widget.key, cutOutSize) | |||
| .._startScan(widget.key, cutOutSize); | |||
| static _CreationParams fromWidget(double width, double height) { | |||
| return _CreationParams( | |||
| width: width, | |||
| height: height, | |||
| ); | |||
| // Initialize the controller for controlling the QRView | |||
| if (widget.onQRViewCreated != null) { | |||
| widget.onQRViewCreated(controller); | |||
| } | |||
| } | |||
| } | |||
| class _QrCameraSettings { | |||
| _QrCameraSettings({ | |||
| this.cameraFacing, | |||
| }); | |||
| final double width; | |||
| final double height; | |||
| final CameraFacing cameraFacing; | |||
| Map<String, dynamic> toMap() { | |||
| return <String, dynamic>{ | |||
| 'width': width, | |||
| 'height': height, | |||
| 'cameraFacing': cameraFacing.index, | |||
| }; | |||
| } | |||
| } | |||
| class QRViewController { | |||
| QRViewController._(int id, GlobalKey qrKey, double scanArea) | |||
| : _channel = MethodChannel('net.touchcapture.qr.flutterqr/qrview_$id') { | |||
| updateDimensions(qrKey, scanArea: scanArea); | |||
| _channel.setMethodCallHandler( | |||
| (call) async { | |||
| switch (call.method) { | |||
| case scanMethodCall: | |||
| if (call.arguments != null) { | |||
| final args = call.arguments as Map; | |||
| final code = args['code'] as String; | |||
| final rawType = args['type'] as String; | |||
| // Raw bytes are only supported by Android. | |||
| final rawBytes = args['rawBytes'] as List<int>; | |||
| final format = _formatNames[rawType]; | |||
| if (format != null) { | |||
| final barcode = Barcode(code, format, rawBytes); | |||
| _scanUpdateController.sink.add(barcode); | |||
| } else { | |||
| throw Exception('Unexpected barcode type $rawType'); | |||
| } | |||
| } | |||
| } | |||
| }, | |||
| ); | |||
| QRViewController._(MethodChannel channel, GlobalKey qrKey, double scanArea) | |||
| : _channel = channel { | |||
| _channel.setMethodCallHandler(_onMethodCall); | |||
| } | |||
| static const scanMethodCall = 'onRecognizeQR'; | |||
| final MethodChannel _channel; | |||
| final StreamController<Barcode> _scanUpdateController = | |||
| StreamController<Barcode>(); | |||
| Stream<Barcode> get scannedDataStream => _scanUpdateController.stream; | |||
| Future<void> _onMethodCall(MethodCall call) async { | |||
| switch (call.method) { | |||
| case 'onRecognizeQR': | |||
| if (call.arguments != null) { | |||
| final args = call.arguments as Map; | |||
| final code = args['code'] as String; | |||
| final rawType = args['type'] as String; | |||
| // Raw bytes are only supported by Android. | |||
| final rawBytes = args['rawBytes'] as List<int>; | |||
| final format = _formatNames[rawType]; | |||
| if (format != null) { | |||
| final barcode = Barcode(code, format, rawBytes); | |||
| _scanUpdateController.sink.add(barcode); | |||
| } else { | |||
| throw Exception('Unexpected barcode type $rawType'); | |||
| } | |||
| } | |||
| } | |||
| } | |||
| /// Starts the barcode scanner | |||
| Future<void> _startScan(GlobalKey key, double cutOutSize, ) async { | |||
| // We need to update the dimension before the scan is started. | |||
| QRViewController.updateDimensions(key, _channel, scanArea: cutOutSize); | |||
| return _channel.invokeMethod('startScan'); | |||
| } | |||
| /// Flips the camera between available modes | |||
| void flipCamera() { | |||
| _channel.invokeMethod('flipCamera'); | |||
| } | |||
| /// Toggles the flashlight between available modes | |||
| void toggleFlash() { | |||
| _channel.invokeMethod('toggleFlash'); | |||
| } | |||
| /// Pauses barcode scanning | |||
| void pauseCamera() { | |||
| _channel.invokeMethod('pauseCamera'); | |||
| } | |||
| /// Resumes barcode scanning | |||
| void resumeCamera() { | |||
| _channel.invokeMethod('resumeCamera'); | |||
| } | |||
| /// Disposes the barcode stream. | |||
| void dispose() { | |||
| _scanUpdateController.close(); | |||
| } | |||
| void updateDimensions(GlobalKey key, {double scanArea}) { | |||
| /// Updates the view dimensions for iOS. | |||
| static void updateDimensions(GlobalKey key, MethodChannel channel, {double scanArea}) { | |||
| if (defaultTargetPlatform == TargetPlatform.iOS) { | |||
| final RenderBox renderBox = key.currentContext.findRenderObject(); | |||
| _channel.invokeMethod('setDimensions', { | |||
| channel.invokeMethod('setDimensions', { | |||
| 'width': renderBox.size.width, | |||
| 'height': renderBox.size.height, | |||
| 'scanArea': scanArea ?? 0 | |||