Showing
8 changed files
with
117 additions
and
15 deletions
| 1 | +## Next | ||
| 2 | + | ||
| 3 | +Improvements: | ||
| 4 | +* [Web] Automatically inject js libraries | ||
| 5 | + | ||
| 1 | ## 3.0.0-beta.4 | 6 | ## 3.0.0-beta.4 |
| 2 | Fixes: | 7 | Fixes: |
| 3 | * Fixes a permission bug on Android where denying the permission would cause an infinite loop of permission requests. | 8 | * Fixes a permission bug on Android where denying the permission would cause an infinite loop of permission requests. |
| @@ -53,13 +53,6 @@ Ensure that you granted camera permission in XCode -> Signing & Capabilities: | @@ -53,13 +53,6 @@ Ensure that you granted camera permission in XCode -> Signing & Capabilities: | ||
| 53 | 53 | ||
| 54 | <img width="696" alt="Screenshot of XCode where Camera is checked" src="https://user-images.githubusercontent.com/24459435/193464115-d76f81d0-6355-4cb2-8bee-538e413a3ad0.png"> | 54 | <img width="696" alt="Screenshot of XCode where Camera is checked" src="https://user-images.githubusercontent.com/24459435/193464115-d76f81d0-6355-4cb2-8bee-538e413a3ad0.png"> |
| 55 | 55 | ||
| 56 | -### Web | ||
| 57 | -Add this to `web/index.html`: | ||
| 58 | - | ||
| 59 | -```html | ||
| 60 | -<script type="text/javascript" src="https://unpkg.com/@zxing/library@0.19.1"></script> | ||
| 61 | -``` | ||
| 62 | - | ||
| 63 | ## Usage | 56 | ## Usage |
| 64 | 57 | ||
| 65 | Import `package:mobile_scanner/mobile_scanner.dart`, and use the widget with or without the controller. | 58 | Import `package:mobile_scanner/mobile_scanner.dart`, and use the widget with or without the controller. |
| @@ -28,8 +28,6 @@ | @@ -28,8 +28,6 @@ | ||
| 28 | 28 | ||
| 29 | <title>example</title> | 29 | <title>example</title> |
| 30 | <link rel="manifest" href="manifest.json"> | 30 | <link rel="manifest" href="manifest.json"> |
| 31 | - <script src="https://cdn.jsdelivr.net/npm/jsqr@1.4.0/dist/jsQR.min.js"></script> | ||
| 32 | - <script type="text/javascript" src="https://unpkg.com/@zxing/library@0.19.1"></script> | ||
| 33 | </head> | 31 | </head> |
| 34 | <body> | 32 | <body> |
| 35 | <!-- This script installs service_worker.js to provide PWA functionality to | 33 | <!-- This script installs service_worker.js to provide PWA functionality to |
| @@ -8,6 +8,7 @@ import 'package:mobile_scanner/mobile_scanner_web.dart'; | @@ -8,6 +8,7 @@ import 'package:mobile_scanner/mobile_scanner_web.dart'; | ||
| 8 | import 'package:mobile_scanner/src/barcode_utility.dart'; | 8 | import 'package:mobile_scanner/src/barcode_utility.dart'; |
| 9 | import 'package:mobile_scanner/src/enums/camera_facing.dart'; | 9 | import 'package:mobile_scanner/src/enums/camera_facing.dart'; |
| 10 | import 'package:mobile_scanner/src/objects/barcode.dart'; | 10 | import 'package:mobile_scanner/src/objects/barcode.dart'; |
| 11 | +import 'package:mobile_scanner/src/web/utils.dart'; | ||
| 11 | 12 | ||
| 12 | /// This plugin is the web implementation of mobile_scanner. | 13 | /// This plugin is the web implementation of mobile_scanner. |
| 13 | /// It only supports QR codes. | 14 | /// It only supports QR codes. |
| @@ -25,6 +26,8 @@ class MobileScannerWebPlugin { | @@ -25,6 +26,8 @@ class MobileScannerWebPlugin { | ||
| 25 | ); | 26 | ); |
| 26 | final MobileScannerWebPlugin instance = MobileScannerWebPlugin(); | 27 | final MobileScannerWebPlugin instance = MobileScannerWebPlugin(); |
| 27 | 28 | ||
| 29 | + injectJSLibraries(barCodeReader.jsLibraries); | ||
| 30 | + | ||
| 28 | channel.setMethodCallHandler(instance.handleMethodCall); | 31 | channel.setMethodCallHandler(instance.handleMethodCall); |
| 29 | event.setController(instance.controller); | 32 | event.setController(instance.controller); |
| 30 | } | 33 | } |
| @@ -7,6 +7,24 @@ import 'package:mobile_scanner/src/enums/camera_facing.dart'; | @@ -7,6 +7,24 @@ import 'package:mobile_scanner/src/enums/camera_facing.dart'; | ||
| 7 | import 'package:mobile_scanner/src/objects/barcode.dart'; | 7 | import 'package:mobile_scanner/src/objects/barcode.dart'; |
| 8 | import 'package:mobile_scanner/src/web/media.dart'; | 8 | import 'package:mobile_scanner/src/web/media.dart'; |
| 9 | 9 | ||
| 10 | +class JsLibrary { | ||
| 11 | + /// The name of global variable where library is stored. | ||
| 12 | + /// Used to properly import the library if [usesRequireJs] flag is true | ||
| 13 | + final String contextName; | ||
| 14 | + final String url; | ||
| 15 | + | ||
| 16 | + /// If js code checks for 'define' variable. | ||
| 17 | + /// E.g. if at the beginning you see code like | ||
| 18 | + /// if (typeof define === "function" && define.amd) | ||
| 19 | + final bool usesRequireJs; | ||
| 20 | + | ||
| 21 | + const JsLibrary({ | ||
| 22 | + required this.contextName, | ||
| 23 | + required this.url, | ||
| 24 | + required this.usesRequireJs, | ||
| 25 | + }); | ||
| 26 | +} | ||
| 27 | + | ||
| 10 | abstract class WebBarcodeReaderBase { | 28 | abstract class WebBarcodeReaderBase { |
| 11 | /// Timer used to capture frames to be analyzed | 29 | /// Timer used to capture frames to be analyzed |
| 12 | Duration frameInterval = const Duration(milliseconds: 200); | 30 | Duration frameInterval = const Duration(milliseconds: 200); |
| @@ -21,6 +39,9 @@ abstract class WebBarcodeReaderBase { | @@ -21,6 +39,9 @@ abstract class WebBarcodeReaderBase { | ||
| 21 | int get videoWidth; | 39 | int get videoWidth; |
| 22 | int get videoHeight; | 40 | int get videoHeight; |
| 23 | 41 | ||
| 42 | + /// JS libraries to be injected into html page. | ||
| 43 | + List<JsLibrary> get jsLibraries; | ||
| 44 | + | ||
| 24 | /// Starts streaming video | 45 | /// Starts streaming video |
| 25 | Future<void> start({ | 46 | Future<void> start({ |
| 26 | required CameraFacing cameraFacing, | 47 | required CameraFacing cameraFacing, |
| @@ -20,11 +20,14 @@ class Code { | @@ -20,11 +20,14 @@ class Code { | ||
| 20 | external Uint8ClampedList get binaryData; | 20 | external Uint8ClampedList get binaryData; |
| 21 | } | 21 | } |
| 22 | 22 | ||
| 23 | +const jsqrLibrary = JsLibrary( | ||
| 24 | + contextName: 'jsQR', | ||
| 25 | + url: 'https://cdn.jsdelivr.net/npm/jsqr@1.4.0/dist/jsQR.min.js', | ||
| 26 | + usesRequireJs: true, | ||
| 27 | +); | ||
| 28 | + | ||
| 23 | /// Barcode reader that uses jsQR library. | 29 | /// Barcode reader that uses jsQR library. |
| 24 | /// jsQR supports only QR codes format. | 30 | /// jsQR supports only QR codes format. |
| 25 | -/// | ||
| 26 | -/// Include jsQR to your index.html file: | ||
| 27 | -/// <script src="https://cdn.jsdelivr.net/npm/jsqr@1.4.0/dist/jsQR.min.js"></script> | ||
| 28 | class JsQrCodeReader extends WebBarcodeReaderBase | 31 | class JsQrCodeReader extends WebBarcodeReaderBase |
| 29 | with InternalStreamCreation, InternalTorchDetection { | 32 | with InternalStreamCreation, InternalTorchDetection { |
| 30 | JsQrCodeReader({required super.videoContainer}); | 33 | JsQrCodeReader({required super.videoContainer}); |
| @@ -33,6 +36,9 @@ class JsQrCodeReader extends WebBarcodeReaderBase | @@ -33,6 +36,9 @@ class JsQrCodeReader extends WebBarcodeReaderBase | ||
| 33 | bool get isStarted => localMediaStream != null; | 36 | bool get isStarted => localMediaStream != null; |
| 34 | 37 | ||
| 35 | @override | 38 | @override |
| 39 | + List<JsLibrary> get jsLibraries => [jsqrLibrary]; | ||
| 40 | + | ||
| 41 | + @override | ||
| 36 | Future<void> start({ | 42 | Future<void> start({ |
| 37 | required CameraFacing cameraFacing, | 43 | required CameraFacing cameraFacing, |
| 38 | List<BarcodeFormat>? formats, | 44 | List<BarcodeFormat>? formats, |
lib/src/web/utils.dart
0 → 100644
| 1 | +import 'dart:async'; | ||
| 2 | +import 'dart:html' as html; | ||
| 3 | +import 'dart:js' show context; | ||
| 4 | + | ||
| 5 | +import 'package:js/js.dart'; | ||
| 6 | +import 'package:mobile_scanner/src/web/base.dart'; | ||
| 7 | + | ||
| 8 | +Future<void> loadScript(JsLibrary library) async { | ||
| 9 | + // ignore: avoid_dynamic_calls | ||
| 10 | + if (library.usesRequireJs && context['define']?['amd'] != null) { | ||
| 11 | + // see https://github.com/dart-lang/sdk/issues/33979 | ||
| 12 | + return loadScriptUsingRequireJS(library.contextName, library.url); | ||
| 13 | + } else { | ||
| 14 | + return loadScriptUsingScriptTag(library.url); | ||
| 15 | + } | ||
| 16 | +} | ||
| 17 | + | ||
| 18 | +Future<void> loadScriptUsingScriptTag(String url) { | ||
| 19 | + final script = html.ScriptElement() | ||
| 20 | + ..async = true | ||
| 21 | + ..defer = false | ||
| 22 | + ..crossOrigin = 'anonymous' | ||
| 23 | + ..type = 'text/javascript' | ||
| 24 | + // ignore: unsafe_html | ||
| 25 | + ..src = url; | ||
| 26 | + | ||
| 27 | + html.document.head!.append(script); | ||
| 28 | + | ||
| 29 | + return script.onLoad.first; | ||
| 30 | +} | ||
| 31 | + | ||
| 32 | +Future<void> loadScriptUsingRequireJS(String packageName, String url) { | ||
| 33 | + final Completer completer = Completer(); | ||
| 34 | + final String eventName = '_${packageName}Loaded'; | ||
| 35 | + | ||
| 36 | + context.callMethod( | ||
| 37 | + 'addEventListener', | ||
| 38 | + [eventName, allowInterop((_) => completer.complete())], | ||
| 39 | + ); | ||
| 40 | + | ||
| 41 | + final script = html.ScriptElement() | ||
| 42 | + ..type = 'text/javascript' | ||
| 43 | + ..async = false | ||
| 44 | + ..defer = false | ||
| 45 | + ..text = ''' | ||
| 46 | + require(["$url"], (package) => { | ||
| 47 | + window.$packageName = package; | ||
| 48 | + const event = new Event("$eventName"); | ||
| 49 | + dispatchEvent(event); | ||
| 50 | + }) | ||
| 51 | + '''; | ||
| 52 | + | ||
| 53 | + html.document.head!.append(script); | ||
| 54 | + | ||
| 55 | + return completer.future; | ||
| 56 | +} | ||
| 57 | + | ||
| 58 | +/// Injects JS [libraries] | ||
| 59 | +/// | ||
| 60 | +/// Returns a [Future] that resolves when all of the `script` tags `onLoad` events trigger. | ||
| 61 | +Future<void> injectJSLibraries(List<JsLibrary> libraries) { | ||
| 62 | + final List<Future<void>> loading = []; | ||
| 63 | + | ||
| 64 | + for (final library in libraries) { | ||
| 65 | + final future = loadScript(library); | ||
| 66 | + loading.add(future); | ||
| 67 | + } | ||
| 68 | + | ||
| 69 | + return Future.wait(loading); | ||
| 70 | +} |
| @@ -168,10 +168,13 @@ extension JsZXingBrowserMultiFormatReaderExt | @@ -168,10 +168,13 @@ extension JsZXingBrowserMultiFormatReaderExt | ||
| 168 | external MediaStream? stream; | 168 | external MediaStream? stream; |
| 169 | } | 169 | } |
| 170 | 170 | ||
| 171 | +const zxingJsLibrary = JsLibrary( | ||
| 172 | + contextName: 'ZXing', | ||
| 173 | + url: 'https://unpkg.com/@zxing/library@0.19.1', | ||
| 174 | + usesRequireJs: true, | ||
| 175 | +); | ||
| 176 | + | ||
| 171 | /// Barcode reader that uses zxing-js library. | 177 | /// Barcode reader that uses zxing-js library. |
| 172 | -/// | ||
| 173 | -/// Include zxing-js to your index.html file: | ||
| 174 | -/// <script type="text/javascript" src="https://unpkg.com/@zxing/library@0.19.1"></script> | ||
| 175 | class ZXingBarcodeReader extends WebBarcodeReaderBase | 178 | class ZXingBarcodeReader extends WebBarcodeReaderBase |
| 176 | with InternalStreamCreation, InternalTorchDetection { | 179 | with InternalStreamCreation, InternalTorchDetection { |
| 177 | JsZXingBrowserMultiFormatReader? _reader; | 180 | JsZXingBrowserMultiFormatReader? _reader; |
| @@ -182,6 +185,9 @@ class ZXingBarcodeReader extends WebBarcodeReaderBase | @@ -182,6 +185,9 @@ class ZXingBarcodeReader extends WebBarcodeReaderBase | ||
| 182 | bool get isStarted => localMediaStream != null; | 185 | bool get isStarted => localMediaStream != null; |
| 183 | 186 | ||
| 184 | @override | 187 | @override |
| 188 | + List<JsLibrary> get jsLibraries => [zxingJsLibrary]; | ||
| 189 | + | ||
| 190 | + @override | ||
| 185 | Future<void> start({ | 191 | Future<void> start({ |
| 186 | required CameraFacing cameraFacing, | 192 | required CameraFacing cameraFacing, |
| 187 | List<BarcodeFormat>? formats, | 193 | List<BarcodeFormat>? formats, |
-
Please register or login to post a comment