Navaron Bracke

reimplement mobile_scanner web with platform interface

  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 }