Showing
1 changed file
with
161 additions
and
0 deletions
| 1 | +import 'dart:async'; | ||
| 2 | +import 'dart:ui_web' as ui_web; | ||
| 3 | + | ||
| 4 | +import 'package:flutter/widgets.dart'; | ||
| 1 | import 'package:flutter_web_plugins/flutter_web_plugins.dart'; | 5 | import 'package:flutter_web_plugins/flutter_web_plugins.dart'; |
| 6 | +import 'package:mobile_scanner/src/enums/mobile_scanner_error_code.dart'; | ||
| 7 | +import 'package:mobile_scanner/src/enums/torch_state.dart'; | ||
| 8 | +import 'package:mobile_scanner/src/mobile_scanner_exception.dart'; | ||
| 2 | import 'package:mobile_scanner/src/mobile_scanner_platform_interface.dart'; | 9 | import 'package:mobile_scanner/src/mobile_scanner_platform_interface.dart'; |
| 10 | +import 'package:mobile_scanner/src/mobile_scanner_view_attributes.dart'; | ||
| 11 | +import 'package:mobile_scanner/src/objects/barcode_capture.dart'; | ||
| 12 | +import 'package:mobile_scanner/src/objects/start_options.dart'; | ||
| 13 | +import 'package:mobile_scanner/src/web/barcode_reader.dart'; | ||
| 14 | +import 'package:mobile_scanner/src/web/zxing/zxing_barcode_reader.dart'; | ||
| 15 | +import 'package:web/web.dart'; | ||
| 3 | 16 | ||
| 4 | /// A web implementation of the MobileScannerPlatform of the MobileScanner plugin. | 17 | /// A web implementation of the MobileScannerPlatform of the MobileScanner plugin. |
| 5 | class MobileScannerWeb extends MobileScannerPlatform { | 18 | class MobileScannerWeb extends MobileScannerPlatform { |
| 6 | /// Constructs a [MobileScannerWeb] instance. | 19 | /// Constructs a [MobileScannerWeb] instance. |
| 7 | MobileScannerWeb(); | 20 | MobileScannerWeb(); |
| 8 | 21 | ||
| 22 | + /// The internal barcode reader. | ||
| 23 | + final BarcodeReader _barcodeReader = ZXingBarcodeReader(); | ||
| 24 | + | ||
| 25 | + /// The stream controller for the barcode stream. | ||
| 26 | + final StreamController<BarcodeCapture> _barcodesController = StreamController.broadcast(); | ||
| 27 | + | ||
| 28 | + /// The subscription for the barcode stream. | ||
| 29 | + StreamSubscription<Object?>? _barcodesSubscription; | ||
| 30 | + | ||
| 31 | + /// The container div element for the camera view. | ||
| 32 | + /// | ||
| 33 | + /// This container element is used by the barcode reader. | ||
| 34 | + HTMLDivElement? _divElement; | ||
| 35 | + | ||
| 36 | + /// The view type for the platform view factory. | ||
| 37 | + final String _viewType = 'MobileScannerWeb'; | ||
| 38 | + | ||
| 9 | static void registerWith(Registrar registrar) { | 39 | static void registerWith(Registrar registrar) { |
| 10 | MobileScannerPlatform.instance = MobileScannerWeb(); | 40 | MobileScannerPlatform.instance = MobileScannerWeb(); |
| 11 | } | 41 | } |
| 42 | + | ||
| 43 | + @override | ||
| 44 | + Widget buildCameraView() { | ||
| 45 | + if (!_barcodeReader.isScanning) { | ||
| 46 | + throw const MobileScannerException( | ||
| 47 | + errorCode: MobileScannerErrorCode.controllerUninitialized, | ||
| 48 | + errorDetails: MobileScannerErrorDetails( | ||
| 49 | + message: 'The controller was not yet initialized. Call start() before calling buildCameraView().', | ||
| 50 | + ), | ||
| 51 | + ); | ||
| 52 | + } | ||
| 53 | + | ||
| 54 | + return HtmlElementView(viewType: _viewType); | ||
| 55 | + } | ||
| 56 | + | ||
| 57 | + @override | ||
| 58 | + Future<MobileScannerViewAttributes> start(StartOptions startOptions) async { | ||
| 59 | + await _barcodeReader.maybeLoadLibrary(); | ||
| 60 | + | ||
| 61 | + // Setup the view factory & container element. | ||
| 62 | + if (_divElement == null) { | ||
| 63 | + _divElement = (document.createElement('div') as HTMLDivElement) | ||
| 64 | + ..style.width = '100%' | ||
| 65 | + ..style.height = '100%'; | ||
| 66 | + | ||
| 67 | + ui_web.platformViewRegistry.registerViewFactory( | ||
| 68 | + _viewType, | ||
| 69 | + (int id) => _divElement, | ||
| 70 | + ); | ||
| 71 | + } | ||
| 72 | + | ||
| 73 | + if (_barcodeReader.isScanning) { | ||
| 74 | + throw const MobileScannerException( | ||
| 75 | + errorCode: MobileScannerErrorCode.controllerAlreadyInitialized, | ||
| 76 | + errorDetails: MobileScannerErrorDetails( | ||
| 77 | + message: 'The scanner was already started. Call stop() before calling start() again.', | ||
| 78 | + ), | ||
| 79 | + ); | ||
| 80 | + } | ||
| 81 | + | ||
| 82 | + try { | ||
| 83 | + await _barcodeReader.start( | ||
| 84 | + startOptions, | ||
| 85 | + containerElement: _divElement!, | ||
| 86 | + ); | ||
| 87 | + } catch (error, stackTrace) { | ||
| 88 | + final String errorMessage = error.toString(); | ||
| 89 | + | ||
| 90 | + MobileScannerErrorCode errorCode = MobileScannerErrorCode.genericError; | ||
| 91 | + | ||
| 92 | + if (error is DOMException) { | ||
| 93 | + if (errorMessage.contains('NotFoundError') || errorMessage.contains('NotSupportedError')) { | ||
| 94 | + errorCode = MobileScannerErrorCode.unsupported; | ||
| 95 | + } else if (errorMessage.contains('NotAllowedError')) { | ||
| 96 | + errorCode = MobileScannerErrorCode.permissionDenied; | ||
| 97 | + } | ||
| 98 | + } | ||
| 99 | + | ||
| 100 | + throw MobileScannerException( | ||
| 101 | + errorCode: errorCode, | ||
| 102 | + errorDetails: MobileScannerErrorDetails( | ||
| 103 | + message: errorMessage, | ||
| 104 | + details: stackTrace.toString(), | ||
| 105 | + ), | ||
| 106 | + ); | ||
| 107 | + } | ||
| 108 | + | ||
| 109 | + try { | ||
| 110 | + _barcodesSubscription = _barcodeReader.detectBarcodes().listen((BarcodeCapture barcode) { | ||
| 111 | + if (_barcodesController.isClosed) { | ||
| 112 | + return; | ||
| 113 | + } | ||
| 114 | + | ||
| 115 | + _barcodesController.add(barcode); | ||
| 116 | + }); | ||
| 117 | + | ||
| 118 | + final bool hasTorch = _barcodeReader.hasTorch; | ||
| 119 | + | ||
| 120 | + if (hasTorch && startOptions.torchEnabled) { | ||
| 121 | + await _barcodeReader.setTorchState(TorchState.on).catchError((_) { | ||
| 122 | + // If the torch could not be turned on, continue the camera session anyway. | ||
| 123 | + }); | ||
| 124 | + } | ||
| 125 | + | ||
| 126 | + return MobileScannerViewAttributes( | ||
| 127 | + hasTorch: hasTorch, | ||
| 128 | + size: _barcodeReader.outputSize, | ||
| 129 | + ); | ||
| 130 | + } catch (error, stackTrace) { | ||
| 131 | + throw MobileScannerException( | ||
| 132 | + errorCode: MobileScannerErrorCode.genericError, | ||
| 133 | + errorDetails: MobileScannerErrorDetails( | ||
| 134 | + message: error.toString(), | ||
| 135 | + details: stackTrace.toString(), | ||
| 136 | + ), | ||
| 137 | + ); | ||
| 138 | + } | ||
| 139 | + } | ||
| 140 | + | ||
| 141 | + @override | ||
| 142 | + Future<void> stop() async { | ||
| 143 | + if (_barcodesController.isClosed) { | ||
| 144 | + return; | ||
| 145 | + } | ||
| 146 | + | ||
| 147 | + _barcodesSubscription?.cancel(); | ||
| 148 | + _barcodesSubscription = null; | ||
| 149 | + | ||
| 150 | + // Clear the existing barcodes. | ||
| 151 | + _barcodesController.add(const BarcodeCapture()); | ||
| 152 | + | ||
| 153 | + await _barcodeReader.stop(); | ||
| 154 | + } | ||
| 155 | + | ||
| 156 | + @override | ||
| 157 | + Future<void> updateScanWindow(Rect? window) { | ||
| 158 | + // A scan window is not supported on the web, | ||
| 159 | + // because the scanner does not expose size information for the barcodes. | ||
| 160 | + return Future<void>.value(); | ||
| 161 | + } | ||
| 162 | + | ||
| 163 | + @override | ||
| 164 | + Future<void> dispose() async { | ||
| 165 | + if (_barcodesController.isClosed) { | ||
| 166 | + return; | ||
| 167 | + } | ||
| 168 | + | ||
| 169 | + await stop(); | ||
| 170 | + await _barcodeReader.dispose(); | ||
| 171 | + await _barcodesController.close(); | ||
| 172 | + } | ||
| 12 | } | 173 | } |
-
Please register or login to post a comment