diff --git a/android/src/main/kotlin/net/touchcapture/qr/flutterqr/FlutterQrPlugin.kt b/android/src/main/kotlin/net/touchcapture/qr/flutterqr/FlutterQrPlugin.kt index 53df56e..4210815 100644 --- a/android/src/main/kotlin/net/touchcapture/qr/flutterqr/FlutterQrPlugin.kt +++ b/android/src/main/kotlin/net/touchcapture/qr/flutterqr/FlutterQrPlugin.kt @@ -7,6 +7,7 @@ import io.flutter.embedding.engine.plugins.FlutterPlugin import io.flutter.embedding.engine.plugins.activity.ActivityAware import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding import io.flutter.plugin.common.BinaryMessenger +import io.flutter.plugin.common.MethodChannel import io.flutter.plugin.common.PluginRegistry import io.flutter.plugin.platform.PlatformViewRegistry @@ -63,7 +64,6 @@ class FlutterQrPlugin : FlutterPlugin, ActivityAware { inner class CameraRequestPermissionsListener : PluginRegistry.RequestPermissionsResultListener { override fun onRequestPermissionsResult(id: Int, permissions: Array, grantResults: IntArray): Boolean { if (id == Shared.CAMERA_REQUEST_ID && grantResults[0] == PackageManager.PERMISSION_GRANTED) { - Shared.cameraPermissionContinuation?.run() return true } return false diff --git a/android/src/main/kotlin/net/touchcapture/qr/flutterqr/QRView.kt b/android/src/main/kotlin/net/touchcapture/qr/flutterqr/QRView.kt index 945cb37..5d01244 100644 --- a/android/src/main/kotlin/net/touchcapture/qr/flutterqr/QRView.kt +++ b/android/src/main/kotlin/net/touchcapture/qr/flutterqr/QRView.kt @@ -10,6 +10,7 @@ import android.view.View import com.google.zxing.ResultPoint import android.hardware.Camera.CameraInfo import android.os.Build +import com.google.zxing.BarcodeFormat import com.journeyapps.barcodescanner.BarcodeCallback import com.journeyapps.barcodescanner.BarcodeResult import com.journeyapps.barcodescanner.BarcodeView @@ -22,8 +23,22 @@ class QRView(messenger: BinaryMessenger, id: Int, private val context: Context, private var isTorchOn: Boolean = false private var barcodeView: BarcodeView? = null - private var requestingPermission = false private val channel: MethodChannel + var allowedBarcodeTypes = mutableListOf() + + private val qrCodeTypes = mapOf( + 0 to BarcodeFormat.AZTEC, + 1 to BarcodeFormat.CODE_128, + 2 to BarcodeFormat.CODE_39, + 3 to BarcodeFormat.CODE_93, + 4 to BarcodeFormat.DATA_MATRIX, + 5 to BarcodeFormat.EAN_13, + 6 to BarcodeFormat.EAN_8, + 7 to BarcodeFormat.ITF, + 8 to BarcodeFormat.PDF_417, + 9 to BarcodeFormat.QR_CODE, + 10 to BarcodeFormat.UPC_E + ) init { checkAndRequestPermission(null) @@ -65,68 +80,121 @@ 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() - } - "toggleFlash" -> { - toggleFlash() - } - "pauseCamera" -> { - pauseCamera() - } - "resumeCamera" -> { - resumeCamera() - } + when(call.method) { + "startScan" -> startScan() + "stopScan" -> stopScan() + "flipCamera" -> flipCamera(result) + "toggleFlash" -> toggleFlash(result) + "pauseCamera" -> pauseCamera(result) + "resumeCamera" -> resumeCamera(result) + "requestPermissions" -> checkAndRequestPermission(result) + "getCameraInfo" -> getCameraInfo(result) + "getFlashInfo" -> getFlashInfo(result) +// "showNativeAlertDialog" -> showNativeAlertDialog(result) + "getSystemFeatures" -> getSystemFeatures(result) + "setAllowedBarcodeFormats" -> setBarcodeFormats(call.arguments as List, result) + else -> result.notImplemented() } } - private fun flipCamera() { - barcodeView?.pause() - val settings = barcodeView?.cameraSettings + private fun getCameraInfo(result: MethodChannel.Result) { + if (barcodeView == null) { + return barCodeViewNotSet(result) + } + result.success(barcodeView!!.cameraSettings.requestedCameraId) + } + + private fun flipCamera(result: MethodChannel.Result) { + if (barcodeView == null) { + return barCodeViewNotSet(result) + } + barcodeView!!.pause() + val settings = barcodeView!!.cameraSettings - if(settings?.requestedCameraId == CameraInfo.CAMERA_FACING_FRONT) + if(settings.requestedCameraId == CameraInfo.CAMERA_FACING_FRONT) settings.requestedCameraId = CameraInfo.CAMERA_FACING_BACK else - settings?.requestedCameraId = CameraInfo.CAMERA_FACING_FRONT + settings.requestedCameraId = CameraInfo.CAMERA_FACING_FRONT - barcodeView?.cameraSettings = settings - barcodeView?.resume() + barcodeView!!.cameraSettings = settings + barcodeView!!.resume() + result.success(settings.requestedCameraId) } - private fun toggleFlash() { + private fun getFlashInfo(result: MethodChannel.Result) { + if (barcodeView == null) { + return barCodeViewNotSet(result) + } + result.success(isTorchOn) + } + + private fun toggleFlash(result: MethodChannel.Result) { + if (barcodeView == null) { + return barCodeViewNotSet(result) + } + if (hasFlash()) { - barcodeView?.setTorch(!isTorchOn) + barcodeView!!.setTorch(!isTorchOn) isTorchOn = !isTorchOn + result.success(isTorchOn) + } else { + result.error("404", "This device doesn't support flash", null) } } - private fun pauseCamera() { + private fun pauseCamera(result: MethodChannel.Result) { + if (barcodeView == null) { + return barCodeViewNotSet(result) + } if (barcodeView!!.isPreviewActive) { - barcodeView?.pause() + barcodeView!!.pause() } + result.success(true) } - private fun resumeCamera() { + private fun resumeCamera(result: MethodChannel.Result) { + if (barcodeView == null) { + return barCodeViewNotSet(result) + } if (!barcodeView!!.isPreviewActive) { - barcodeView?.resume() + barcodeView!!.resume() } + result.success(true) } private fun hasFlash(): Boolean { - return context.packageManager - .hasSystemFeature(PackageManager.FEATURE_CAMERA_FLASH) + return hasSystemFeature(PackageManager.FEATURE_CAMERA_FLASH) + } + + private fun hasBackCamera(): Boolean { + return hasSystemFeature(PackageManager.FEATURE_CAMERA) + } + + private fun hasFrontCamera(): Boolean { + return hasSystemFeature(PackageManager.FEATURE_CAMERA_FRONT) + } + + private fun hasSystemFeature(feature: String): Boolean { + return Shared.activity!!.packageManager + .hasSystemFeature(feature) + } + + private fun barCodeViewNotSet(result: MethodChannel.Result) { + result.error("404", "No barcode view found", null) } override fun getView(): View { return initBarCodeView()?.apply { + if (!hasBackCamera()) { + if (!hasFrontCamera()) { + // No camera available! + } else { + this.cameraSettings.requestedCameraId = CameraInfo.CAMERA_FACING_FRONT + } + } else { + this.cameraSettings.requestedCameraId = CameraInfo.CAMERA_FACING_BACK + } resume() }!! } @@ -145,11 +213,14 @@ class QRView(messenger: BinaryMessenger, id: Int, private val context: Context, barcodeView?.decodeContinuous( object : BarcodeCallback { override fun barcodeResult(result: BarcodeResult) { - val code = mapOf( - "code" to result.text, - "type" to result.barcodeFormat.name, - "rawBytes" to result.rawBytes) - channel.invokeMethod("onRecognizeQR", code) + if (allowedBarcodeTypes.size == 0 || allowedBarcodeTypes.contains(result.barcodeFormat)) { + val code = mapOf( + "code" to result.text, + "type" to result.barcodeFormat.name, + "rawBytes" to result.rawBytes) + channel.invokeMethod("onRecognizeQR", code) + } + } override fun possibleResultPoints(resultPoints: List) {} @@ -161,35 +232,55 @@ class QRView(messenger: BinaryMessenger, id: Int, private val context: Context, barcodeView?.stopDecoding() } + private fun getSystemFeatures(result: MethodChannel.Result) { + try { + result.success(mapOf("hasFrontCamera" to hasFrontCamera(), + "hasBackCamera" to hasBackCamera(), "hasFlash" to hasFlash(), + "activeCamera" to barcodeView?.cameraSettings?.requestedCameraId)) + } catch (e: Exception) { + result.error(null, null, null) + } + } + + private fun setBarcodeFormats(arguments: List, result: MethodChannel.Result) { + try { + allowedBarcodeTypes.clear() + arguments.forEach { + allowedBarcodeTypes.add(qrCodeTypes[it]!!) + } + result.success(true) + } catch (e: java.lang.Exception) { + result.error(null, null, null) + } + } + +// private fun showNativeAlertDialog(result: MethodChannel.Result) { +// AlertDialog.Builder(context) +// .setTitle("Scanning Unavailable") +// .setMessage("This app does not have permission to access the camera") +// .setPositiveButton(R.string.ok, null) +// .setCancelable(false) +// .setIcon(R.drawable.ic_dialog_alert) +// .show() +// result.success(true) +// } + private fun hasCameraPermission(): Boolean { return Build.VERSION.SDK_INT < Build.VERSION_CODES.M || Shared.activity?.checkSelfPermission(Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED } private fun checkAndRequestPermission(result: MethodChannel.Result?) { - if (Shared.cameraPermissionContinuation != null) { - result?.error("cameraPermission", "Camera permission request ongoing", null) - } - - Shared.cameraPermissionContinuation = Runnable { - Shared.cameraPermissionContinuation = null - if (!hasCameraPermission()) { - result?.error( - "cameraPermission", "MediaRecorderCamera permission not granted", null) - return@Runnable - } - } - - requestingPermission = false - if (hasCameraPermission()) { - Shared.cameraPermissionContinuation?.run() - } else { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - requestingPermission = true + when { + hasCameraPermission() -> result?.success(true) + Build.VERSION.SDK_INT >= Build.VERSION_CODES.M -> { Shared.activity?.requestPermissions( arrayOf(Manifest.permission.CAMERA), Shared.CAMERA_REQUEST_ID) } + else -> { + result?.error("cameraPermission", "Platform Version to low for camera permission check", null) + } } } } diff --git a/android/src/main/kotlin/net/touchcapture/qr/flutterqr/Shared.kt b/android/src/main/kotlin/net/touchcapture/qr/flutterqr/Shared.kt index 24bc4ab..a5a663d 100644 --- a/android/src/main/kotlin/net/touchcapture/qr/flutterqr/Shared.kt +++ b/android/src/main/kotlin/net/touchcapture/qr/flutterqr/Shared.kt @@ -5,5 +5,4 @@ import android.app.Activity object Shared { const val CAMERA_REQUEST_ID = 513469796 var activity: Activity? = null - var cameraPermissionContinuation: Runnable? = null } \ No newline at end of file diff --git a/example/lib/main.dart b/example/lib/main.dart index 72fdd08..719d582 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -6,11 +6,6 @@ import 'package:qr_code_scanner/qr_code_scanner.dart'; void main() => runApp(MaterialApp(home: QRViewExample())); -const flashOn = 'FLASH ON'; -const flashOff = 'FLASH OFF'; -const frontCamera = 'FRONT CAMERA'; -const backCamera = 'BACK CAMERA'; - class QRViewExample extends StatefulWidget { const QRViewExample({ Key key, @@ -21,9 +16,8 @@ class QRViewExample extends StatefulWidget { } class _QRViewExampleState extends State { + Barcode result; - var flashState = flashOn; - var cameraState = frontCamera; QRViewController controller; final GlobalKey qrKey = GlobalKey(debugLabel: 'QR'); @@ -64,44 +58,33 @@ class _QRViewExampleState extends State { Container( margin: EdgeInsets.all(8), child: RaisedButton( - onPressed: () { - if (controller != null) { - controller.toggleFlash(); - if (_isFlashOn(flashState)) { - setState(() { - flashState = flashOff; - }); - } else { - setState(() { - flashState = flashOn; - }); - } - } - }, - child: - Text(flashState, style: TextStyle(fontSize: 20)), - ), + onPressed: () => setState(() { + controller?.toggleFlash(); + }), + child: FutureBuilder( + future: controller?.getFlashStatus(), + builder: (context, snapshot) { + return Text('Flash: ${snapshot.data}'); + }, + )), ), Container( margin: EdgeInsets.all(8), child: RaisedButton( - onPressed: () { - if (controller != null) { - controller.flipCamera(); - if (_isBackCamera(cameraState)) { - setState(() { - cameraState = frontCamera; - }); - } else { - setState(() { - cameraState = backCamera; - }); - } - } - }, - child: - Text(cameraState, style: TextStyle(fontSize: 20)), - ), + onPressed: () => setState(() { + controller?.flipCamera(); + }), + child: FutureBuilder( + future: controller?.getCameraInfo(), + builder: (context, snapshot) { + if (snapshot.data != null) { + return Text( + 'Camera facing ${describeEnum(snapshot.data)}'); + } else { + return Text('loading'); + } + }, + )), ) ], ), @@ -138,14 +121,6 @@ class _QRViewExampleState extends State { ); } - bool _isFlashOn(String current) { - return flashOn == current; - } - - bool _isBackCamera(String current) { - return backCamera == current; - } - Widget _buildQrView(BuildContext context) { // For this example we check how width or tall the device is and change the scanArea and overlay accordingly. var scanArea = (MediaQuery.of(context).size.width < 400 || @@ -169,7 +144,9 @@ class _QRViewExampleState extends State { } void _onQRViewCreated(QRViewController controller) { - this.controller = controller; + setState(() { + this.controller = controller; + }); controller.scannedDataStream.listen((scanData) { setState(() { result = scanData; diff --git a/ios/Classes/QRView.swift b/ios/Classes/QRView.swift index 3e36441..2e72929 100644 --- a/ios/Classes/QRView.swift +++ b/ios/Classes/QRView.swift @@ -15,6 +15,22 @@ public class QRView:NSObject,FlutterPlatformView { var channel: FlutterMethodChannel var cameraFacing: MTBCamera + var allowedBarcodeTypes: Array = [] + + var QRCodeTypes = [ + 0: AVMetadataObject.ObjectType.aztec, + 1: AVMetadataObject.ObjectType.code128, + 2: AVMetadataObject.ObjectType.code39, + 3: AVMetadataObject.ObjectType.code93, + 4: AVMetadataObject.ObjectType.dataMatrix, + 5: AVMetadataObject.ObjectType.ean13, + 6: AVMetadataObject.ObjectType.ean8, + 7: AVMetadataObject.ObjectType.interleaved2of5, + 8: AVMetadataObject.ObjectType.pdf417, + 9: AVMetadataObject.ObjectType.qr, + 10: AVMetadataObject.ObjectType.upce + ] + public init(withFrame frame: CGRect, withRegistrar registrar: FlutterPluginRegistrar, withId id: Int64, params: Dictionary){ self.registrar = registrar previewView = UIView(frame: frame) @@ -36,13 +52,23 @@ public class QRView:NSObject,FlutterPlatformView { case "startScan": self?.startScan(result) case "flipCamera": - self?.flipCamera() + self?.flipCamera(result) case "toggleFlash": - self?.toggleFlash() + self?.toggleFlash(result) case "pauseCamera": - self?.pauseCamera() + self?.pauseCamera(result) case "resumeCamera": - self?.resumeCamera() + self?.resumeCamera(result) + case "getCameraInfo": + self?.getCameraInfo(result) + case "getFlashInfo": + self?.getFlashInfo(result) + case "showNativeAlertDialog": + self?.showNativeAlertDialog(result) + case "getSystemFeatures": + self?.getSystemFeatures(result) + case "setAllowedBarcodeFormats": + self?.setBarcodeFormats(call.arguments as! Array, result) default: result(FlutterMethodNotImplemented) return @@ -108,7 +134,10 @@ public class QRView:NSObject,FlutterPlatformView { } guard let stringValue = code.stringValue else { continue } let result = ["code": stringValue, "type": typeString] - self?.channel.invokeMethod("onRecognizeQR", arguments: result) + if self!.allowedBarcodeTypes.count == 0 || self!.allowedBarcodeTypes.contains(code.type) { + self?.channel.invokeMethod("onRecognizeQR", arguments: result) + } + } } }) @@ -131,35 +160,106 @@ public class QRView:NSObject,FlutterPlatformView { } } - func flipCamera(){ + func getCameraInfo(_ result: @escaping FlutterResult) -> Void { + if let sc: MTBBarcodeScanner = scanner { + result(sc.camera.rawValue) + } else { + let error = FlutterError(code: "cameraInformationError", message: "Could not get camera information", details: nil) + result(error) + } + } + + func flipCamera(_ result: @escaping FlutterResult){ if let sc: MTBBarcodeScanner = scanner { if sc.hasOppositeCamera() { sc.flipCamera() } + return result(sc.camera.rawValue) } + return result(FlutterError(code: "404", message: "No barcode scanner found", details: nil)) } - func toggleFlash(){ + func getFlashInfo(_ result: @escaping FlutterResult) -> Void { + if let sc: MTBBarcodeScanner = scanner { + result(sc.torchMode.rawValue != 0) + } else { + let error = FlutterError(code: "cameraInformationError", message: "Could not get flash information", details: nil) + result(error) + } + } + + func toggleFlash(_ result: @escaping FlutterResult){ if let sc: MTBBarcodeScanner = scanner { if sc.hasTorch() { sc.toggleTorch() + return result(sc.torchMode == MTBTorchMode(rawValue: 1)) } + return result(FlutterError(code: "404", message: "This device doesn\'t support flash", details: nil)) } + return result(FlutterError(code: "404", message: "No barcode scanner found", details: nil)) } - func pauseCamera() { + func pauseCamera(_ result: @escaping FlutterResult) { if let sc: MTBBarcodeScanner = scanner { if sc.isScanning() { sc.freezeCapture() } + return result(true) } + return result(FlutterError(code: "404", message: "No barcode scanner found", details: nil)) } - func resumeCamera() { + func resumeCamera(_ result: @escaping FlutterResult) { if let sc: MTBBarcodeScanner = scanner { if !sc.isScanning() { sc.unfreezeCapture() } + return result(true) + } + return result(FlutterError(code: "404", message: "No barcode scanner found", details: nil)) + } + + func showNativeAlertDialog(_ result: @escaping FlutterResult) -> Void { + UIAlertView(title: "Scanning Unavailable", message: "This app does not have permission to access the camera", delegate: nil, cancelButtonTitle: nil, otherButtonTitles: "Ok").show() + return result(true) + } + + func getSystemFeatures(_ result: @escaping FlutterResult) -> Void { + if let sc: MTBBarcodeScanner = scanner { + var hasBackCameraVar = false + var hasFrontCameraVar = false + let camera = sc.camera + + if(camera == MTBCamera(rawValue: 0)){ + hasBackCameraVar = true + if sc.hasOppositeCamera() { + hasFrontCameraVar = true + } + }else{ + hasFrontCameraVar = true + if sc.hasOppositeCamera() { + hasBackCameraVar = true + } + } + return result([ + "hasFrontCamera": hasFrontCameraVar, + "hasBackCamera": hasBackCameraVar, + "hasFlash": sc.hasTorch(), + "activeCamera": camera.rawValue + ]) + } + return result(FlutterError(code: "404", message: nil, details: nil)) + } + + func setBarcodeFormats(_ arguments: Array, _ result: @escaping FlutterResult){ + do{ + allowedBarcodeTypes.removeAll() + try arguments.forEach { arg in + allowedBarcodeTypes.append(try QRCodeTypes[arg]!) + } + result(true) + }catch{ + result(FlutterError(code: "404", message: nil, details: nil)) } } -} + } diff --git a/lib/qr_code_scanner.dart b/lib/qr_code_scanner.dart index bc2f4d9..9df6d69 100644 --- a/lib/qr_code_scanner.dart +++ b/lib/qr_code_scanner.dart @@ -1,2 +1,5 @@ export 'src/qr_code_scanner.dart'; export 'src/qr_scanner_overlay_shape.dart'; +export 'src/types/barcode.dart'; +export 'src/types/camera.dart'; +export 'src/types/features.dart'; diff --git a/lib/src/qr_code_scanner.dart b/lib/src/qr_code_scanner.dart index e6f3c3b..427dffc 100644 --- a/lib/src/qr_code_scanner.dart +++ b/lib/src/qr_code_scanner.dart @@ -3,105 +3,16 @@ import 'dart:async'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; -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, - - /// CODABAR 1D format. - codabar, - - /// Code 39 1D format. - code39, - - /// Code 93 1D format. - code93, - - /// Code 128 1D format. - code128, - - /// Data Matrix 2D barcode format. - dataMatrix, - - /// EAN-8 1D format. - ean8, - - /// EAN-13 1D format. - ean13, - - /// ITF (Interleaved Two of Five) 1D format. - itf, - - /// MaxiCode 2D barcode format. - maxicode, - - /// PDF417 format. - pdf417, - - /// QR Code 2D barcode format. - qrcode, - - /// RSS 14 - rss14, - - /// RSS EXPANDED - rssExpanded, - - /// UPC-A 1D format. - upcA, - - /// UPC-E 1D format. - upcE, - - /// UPC/EAN extension format. Not a stand-alone format. - upcEanExtension -} - -const _formatNames = { - 'AZTEC': BarcodeFormat.aztec, - 'CODABAR': BarcodeFormat.codabar, - 'CODE_39': BarcodeFormat.code39, - 'CODE_93': BarcodeFormat.code93, - 'CODE_128': BarcodeFormat.code128, - 'DATA_MATRIX': BarcodeFormat.dataMatrix, - 'EAN_8': BarcodeFormat.ean8, - 'EAN_13': BarcodeFormat.ean13, - 'ITF': BarcodeFormat.itf, - 'MAXICODE': BarcodeFormat.maxicode, - 'PDF_417': BarcodeFormat.pdf417, - 'QR_CODE': BarcodeFormat.qrcode, - 'RSS_14': BarcodeFormat.rss14, - 'RSS_EXPANDED': BarcodeFormat.rssExpanded, - 'UPC_A': BarcodeFormat.upcA, - 'UPC_E': BarcodeFormat.upcE, - '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); - - final String code; - final BarcodeFormat format; +import 'qr_scanner_overlay_shape.dart'; +import 'types/barcode.dart'; +import 'types/barcode_format.dart'; +import 'types/camera.dart'; +import 'types/camera_exception.dart'; +import 'types/features.dart'; - /// Raw bytes are only supported by Android. - final List rawBytes; -} +typedef QRViewCreatedCallback = void Function(QRViewController); +typedef PermissionSetCallback = void Function(QRViewController, bool); /// The [QRView] is the view where the camera and the barcode scanner gets displayed. class QRView extends StatefulWidget { @@ -111,6 +22,8 @@ class QRView extends StatefulWidget { this.overlay, this.overlayMargin = EdgeInsets.zero, this.cameraFacing = CameraFacing.back, + this.onPermissionSet, + this.showNativeAlertDialog = false, }) : assert(key != null), assert(onQRViewCreated != null), super(key: key); @@ -119,6 +32,8 @@ class QRView extends StatefulWidget { final ShapeBorder overlay; final EdgeInsetsGeometry overlayMargin; final CameraFacing cameraFacing; + final PermissionSetCallback onPermissionSet; + final bool showNativeAlertDialog; @override State createState() => _QRViewState(); @@ -201,7 +116,8 @@ class _QRViewState extends State { _channel = MethodChannel('net.touchcapture.qr.flutterqr/qrview_$id'); // Start scan after creation of the view - final controller = QRViewController._(_channel, widget.key, cutOutSize) + final controller = QRViewController._(_channel, widget.key, cutOutSize, + widget.onPermissionSet, widget.showNativeAlertDialog) .._startScan(widget.key, cutOutSize); // Initialize the controller for controlling the QRView @@ -225,10 +141,70 @@ class _QrCameraSettings { } } +const _formatNames = { + 'AZTEC': BarcodeFormat.aztec, + 'CODABAR': BarcodeFormat.codabar, + 'CODE_39': BarcodeFormat.code39, + 'CODE_93': BarcodeFormat.code93, + 'CODE_128': BarcodeFormat.code128, + 'DATA_MATRIX': BarcodeFormat.dataMatrix, + 'EAN_8': BarcodeFormat.ean8, + 'EAN_13': BarcodeFormat.ean13, + 'ITF': BarcodeFormat.itf, + 'MAXICODE': BarcodeFormat.maxicode, + 'PDF_417': BarcodeFormat.pdf417, + 'QR_CODE': BarcodeFormat.qrcode, + 'RSS_14': BarcodeFormat.rss14, + 'RSS_EXPANDED': BarcodeFormat.rssExpanded, + 'UPC_A': BarcodeFormat.upcA, + 'UPC_E': BarcodeFormat.upcE, + 'UPC_EAN_EXTENSION': BarcodeFormat.upcEanExtension, +}; + class QRViewController { - QRViewController._(MethodChannel channel, GlobalKey qrKey, double scanArea) - : _channel = channel { - _channel.setMethodCallHandler(_onMethodCall); + QRViewController._( + MethodChannel channel, + GlobalKey qrKey, + double scanArea, + PermissionSetCallback onPermissionSet, + bool showNativeAlertDialogOnError, + ) : _channel = channel { + _channel.setMethodCallHandler((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; + final format = _formatNames[rawType]; + if (format != null) { + final barcode = Barcode(code, format, rawBytes); + _scanUpdateController.sink.add(barcode); + } else { + throw Exception('Unexpected barcode type $rawType'); + } + } + break; + case 'onPermissionSet': + await getSystemFeatures(); // if we have no permission all features will not be avaible + if (call.arguments != null) { + if (call.arguments as bool) { + _hasPermissions = true; + } else { + _hasPermissions = false; + if (showNativeAlertDialogOnError) { + await showNativeAlertDialog(); + } + } + if (onPermissionSet != null) { + onPermissionSet(this, call.arguments as bool); + } + } + break; + } + }); } final MethodChannel _channel; @@ -237,25 +213,11 @@ class QRViewController { Stream get scannedDataStream => _scanUpdateController.stream; - Future _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; - final format = _formatNames[rawType]; - if (format != null) { - final barcode = Barcode(code, format, rawBytes); - _scanUpdateController.sink.add(barcode); - } else { - throw Exception('Unexpected barcode type $rawType'); - } - } - } - } + SystemFeatures _features; + bool _hasPermissions; + + SystemFeatures get systemFeatures => _features; + bool get hasPermissions => _hasPermissions; /// Starts the barcode scanner Future _startScan( @@ -267,24 +229,86 @@ class QRViewController { return _channel.invokeMethod('startScan'); } + Future getCameraInfo() async { + try { + return CameraFacing + .values[await _channel.invokeMethod('getCameraInfo') as int]; + } on PlatformException catch (e) { + throw CameraException(e.code, e.message); + } + } + /// Flips the camera between available modes - void flipCamera() { - _channel.invokeMethod('flipCamera'); + Future flipCamera() async { + try { + return CameraFacing + .values[await _channel.invokeMethod('flipCamera') as int]; + } on PlatformException catch (e) { + throw CameraException(e.code, e.message); + } + } + + /// Get flashlight status + Future getFlashStatus() async { + try { + return await _channel.invokeMethod('getFlashInfo'); + } on PlatformException catch (e) { + throw CameraException(e.code, e.message); + } } /// Toggles the flashlight between available modes - void toggleFlash() { - _channel.invokeMethod('toggleFlash'); + Future toggleFlash() async { + try { + await _channel.invokeMethod('toggleFlash') as bool; + } on PlatformException catch (e) { + throw CameraException(e.code, e.message); + } } /// Pauses barcode scanning - void pauseCamera() { - _channel.invokeMethod('pauseCamera'); + Future pauseCamera() async { + try { + await _channel.invokeMethod('pauseCamera'); + } on PlatformException catch (e) { + throw CameraException(e.code, e.message); + } } /// Resumes barcode scanning - void resumeCamera() { - _channel.invokeMethod('resumeCamera'); + Future resumeCamera() async { + try { + await _channel.invokeMethod('resumeCamera'); + } on PlatformException catch (e) { + throw CameraException(e.code, e.message); + } + } + + Future showNativeAlertDialog() async { + try { + await _channel.invokeMethod('showNativeAlertDialog'); + } on PlatformException catch (e) { + throw CameraException(e.code, e.message); + } + } + + Future setAllowedBarcodeTypes(List list) async { + try { + await _channel.invokeMethod('setAllowedBarcodeFormats', + list?.map((e) => e.asInt())?.toList() ?? []); + } on PlatformException catch (e) { + throw CameraException(e.code, e.message); + } + } + + Future getSystemFeatures() async { + try { + var features = + await _channel.invokeMapMethod('getSystemFeatures'); + return SystemFeatures.fromJson(features); + } on PlatformException catch (e) { + throw CameraException(e.code, e.message); + } } /// Disposes the barcode stream. diff --git a/lib/src/types/barcode.dart b/lib/src/types/barcode.dart new file mode 100644 index 0000000..bf2f101 --- /dev/null +++ b/lib/src/types/barcode.dart @@ -0,0 +1,16 @@ +import 'barcode_format.dart'; + +/// 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); + + final String code; + final BarcodeFormat format; + + /// Raw bytes are only supported by Android. + final List rawBytes; +} \ No newline at end of file diff --git a/lib/src/types/barcode_format.dart b/lib/src/types/barcode_format.dart new file mode 100644 index 0000000..8ae0716 --- /dev/null +++ b/lib/src/types/barcode_format.dart @@ -0,0 +1,121 @@ +enum BarcodeFormat { + /// Aztec 2D barcode format. + aztec, + + /// CODABAR 1D format. + codabar, + + /// Code 39 1D format. + code39, + + /// Code 93 1D format. + code93, + + /// Code 128 1D format. + code128, + + /// Data Matrix 2D barcode format. + dataMatrix, + + /// EAN-8 1D format. + ean8, + + /// EAN-13 1D format. + ean13, + + /// ITF (Interleaved Two of Five) 1D format. + itf, + + /// MaxiCode 2D barcode format. + maxicode, + + /// PDF417 format. + pdf417, + + /// QR Code 2D barcode format. + qrcode, + + /// RSS 14 + rss14, + + /// RSS EXPANDED + rssExpanded, + + /// UPC-A 1D format. + upcA, + + /// UPC-E 1D format. + upcE, + + /// UPC/EAN extension format. Not a stand-alone format. + upcEanExtension +} + +extension BarcodeTypesExtension on BarcodeFormat { + int asInt() { + return index; + } + + BarcodeFormat fromString(String format) { + + } + + String get formatName { + switch (this) { + + case BarcodeFormat.aztec: + return 'AZTEC'; + break; + case BarcodeFormat.codabar: + return 'CODABAR'; + break; + case BarcodeFormat.code39: + return 'CODE_39'; + break; + case BarcodeFormat.code93: + return 'CODE_93'; + break; + case BarcodeFormat.code128: + return 'CODE_128'; + break; + case BarcodeFormat.dataMatrix: + return 'DATA_MATRIX'; + break; + case BarcodeFormat.ean8: + return 'EAN_8'; + break; + case BarcodeFormat.ean13: + return 'EAN_13'; + break; + case BarcodeFormat.itf: + return 'ITF'; + break; + case BarcodeFormat.maxicode: + return 'MAXICODE'; + break; + case BarcodeFormat.pdf417: + return 'PDF_417'; + break; + case BarcodeFormat.qrcode: + return 'QR_CODE'; + break; + case BarcodeFormat.rss14: + return 'RSS14'; + break; + case BarcodeFormat.rssExpanded: + return 'RSS_EXPANDED'; + break; + case BarcodeFormat.upcA: + return 'UPC_A'; + break; + case BarcodeFormat.upcE: + return 'UPC_E'; + break; + case BarcodeFormat.upcEanExtension: + return 'UPC_EAN_EXTENSION'; + break; + } + return 'NOT_VALID'; + } + +} \ No newline at end of file diff --git a/lib/src/types/camera.dart b/lib/src/types/camera.dart new file mode 100644 index 0000000..6abbef5 --- /dev/null +++ b/lib/src/types/camera.dart @@ -0,0 +1,7 @@ +enum CameraFacing { + /// Shows back facing camera. + back, + + /// Shows front facing camera. + front +} \ No newline at end of file diff --git a/lib/src/types/camera_exception.dart b/lib/src/types/camera_exception.dart new file mode 100644 index 0000000..84a7881 --- /dev/null +++ b/lib/src/types/camera_exception.dart @@ -0,0 +1,14 @@ +/// This is thrown when the plugin reports an error. +class CameraException implements Exception { + /// Creates a new camera exception with the given error code and description. + CameraException(this.code, this.description); + + /// Error code. + String code; + + /// Textual description of the error. + String description; + + @override + String toString() => 'CameraException($code, $description)'; +} \ No newline at end of file diff --git a/lib/src/types/features.dart b/lib/src/types/features.dart new file mode 100644 index 0000000..ebe92d5 --- /dev/null +++ b/lib/src/types/features.dart @@ -0,0 +1,13 @@ +class SystemFeatures { + SystemFeatures(this.hasFlash, this.hasBackCamera, this.hasFrontCamera); + + factory SystemFeatures.fromJson(Map features) => + SystemFeatures( + features['hasFlash'] ?? false, + features['hasBackCamera'] ?? false, + features['hasFrontCamera'] ?? false); + + final bool hasFlash; + final bool hasFrontCamera; + final bool hasBackCamera; +} \ No newline at end of file diff --git a/pubspec.yaml b/pubspec.yaml index 1fe733a..88f840a 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -5,7 +5,7 @@ homepage: https://juliuscanute.com repository: https://github.com/juliuscanute/qr_code_scanner environment: - sdk: ">=2.3.0 <3.0.0" + sdk: ">=2.6.0 <3.0.0" flutter: ^1.10.0 dependencies: