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 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, }; 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; } class QRView extends StatefulWidget { const QRView({ @required Key key, @required this.onQRViewCreated, this.overlay, this.overlayMargin = EdgeInsets.zero, }) : assert(key != null), assert(onQRViewCreated != null), super(key: key); final QRViewCreatedCallback onQRViewCreated; final ShapeBorder overlay; final EdgeInsetsGeometry overlayMargin; @override State createState() => _QRViewState(); } class _QRViewState extends State { @override Widget build(BuildContext context) { return Stack( children: [ _getPlatformQrView(widget.key), if (widget.overlay != null) Container( padding: widget.overlayMargin, decoration: ShapeDecoration( shape: widget.overlay, ), ) else Container(), ], ); } Widget _getPlatformQrView(GlobalKey key) { Widget _platformQrView; switch (defaultTargetPlatform) { case TargetPlatform.android: _platformQrView = AndroidView( viewType: 'net.touchcapture.qr.flutterqr/qrview', onPlatformViewCreated: _onPlatformViewCreated, ); break; case TargetPlatform.iOS: _platformQrView = UiKitView( viewType: 'net.touchcapture.qr.flutterqr/qrview', onPlatformViewCreated: _onPlatformViewCreated, creationParams: _CreationParams.fromWidget(MediaQuery.of(context).size.width, 400) .toMap(), creationParamsCodec: StandardMessageCodec(), ); break; default: throw UnsupportedError( "Trying to use the default webview implementation for $defaultTargetPlatform but there isn't a default one"); } return _platformQrView; } 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)); } } class _CreationParams { _CreationParams({this.width, this.height}); static _CreationParams fromWidget(double width, double height) { return _CreationParams( width: width, height: height, ); } final double width; final double height; Map toMap() { return { 'width': width, 'height': height, }; } } 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; final format = _formatNames[rawType]; if (format != null) { final barcode = Barcode(code, format, rawBytes); _scanUpdateController.sink.add(barcode); } else { throw Exception('Unexpected barcode type $rawType'); } } } }, ); } static const scanMethodCall = 'onRecognizeQR'; final MethodChannel _channel; final StreamController _scanUpdateController = StreamController(); Stream get scannedDataStream => _scanUpdateController.stream; void flipCamera() { _channel.invokeMethod('flipCamera'); } void toggleFlash() { _channel.invokeMethod('toggleFlash'); } void pauseCamera() { _channel.invokeMethod('pauseCamera'); } void resumeCamera() { _channel.invokeMethod('resumeCamera'); } void dispose() { _scanUpdateController.close(); } void updateDimensions(GlobalKey key, {double scanArea}) { if (defaultTargetPlatform == TargetPlatform.iOS) { final RenderBox renderBox = key.currentContext.findRenderObject(); _channel.invokeMethod('setDimensions', { 'width': renderBox.size.width, 'height': renderBox.size.height, 'scanArea': scanArea ?? 0 }); } } }