Julian Steenbakker
Committed by GitHub

Merge pull request #679 from navaronbracke/fix_zxing_bug_on_web

fix: fix mobile scanner startup bug on web
1 # This file tracks properties of this Flutter project. 1 # This file tracks properties of this Flutter project.
2 # Used by Flutter tool to assess capabilities and perform upgrades etc. 2 # Used by Flutter tool to assess capabilities and perform upgrades etc.
3 # 3 #
4 -# This file should be version controlled and should not be manually edited. 4 +# This file should be version controlled.
5 5
6 version: 6 version:
7 - revision: 5f105a6ca7a5ac7b8bc9b241f4c2d86f4188cf5c 7 + revision: 796c8ef79279f9c774545b3771238c3098dbefab
8 channel: stable 8 channel: stable
9 9
10 project_type: plugin 10 project_type: plugin
  11 +
  12 +# Tracks metadata for the flutter migrate command
  13 +migration:
  14 + platforms:
  15 + - platform: root
  16 + create_revision: 796c8ef79279f9c774545b3771238c3098dbefab
  17 + base_revision: 796c8ef79279f9c774545b3771238c3098dbefab
  18 + - platform: android
  19 + create_revision: 796c8ef79279f9c774545b3771238c3098dbefab
  20 + base_revision: 796c8ef79279f9c774545b3771238c3098dbefab
  21 + - platform: ios
  22 + create_revision: 796c8ef79279f9c774545b3771238c3098dbefab
  23 + base_revision: 796c8ef79279f9c774545b3771238c3098dbefab
  24 + - platform: macos
  25 + create_revision: 796c8ef79279f9c774545b3771238c3098dbefab
  26 + base_revision: 796c8ef79279f9c774545b3771238c3098dbefab
  27 + - platform: web
  28 + create_revision: 796c8ef79279f9c774545b3771238c3098dbefab
  29 + base_revision: 796c8ef79279f9c774545b3771238c3098dbefab
  30 +
  31 + # User provided section
  32 +
  33 + # List of Local paths (relative to this file) that should be
  34 + # ignored by the migrate tool.
  35 + #
  36 + # Files that are not part of the templates will be ignored by default.
  37 + unmanaged_files:
  38 + - 'lib/main.dart'
  39 + - 'ios/Runner.xcodeproj/project.pbxproj'
@@ -50,6 +50,7 @@ class _BarcodeListScannerWithControllerState @@ -50,6 +50,7 @@ class _BarcodeListScannerWithControllerState
50 @override 50 @override
51 Widget build(BuildContext context) { 51 Widget build(BuildContext context) {
52 return Scaffold( 52 return Scaffold(
  53 + appBar: AppBar(title: const Text('With ValueListenableBuilder')),
53 backgroundColor: Colors.black, 54 backgroundColor: Colors.black,
54 body: Builder( 55 body: Builder(
55 builder: (context) { 56 builder: (context) {
@@ -50,6 +50,7 @@ class _BarcodeScannerWithControllerState @@ -50,6 +50,7 @@ class _BarcodeScannerWithControllerState
50 @override 50 @override
51 Widget build(BuildContext context) { 51 Widget build(BuildContext context) {
52 return Scaffold( 52 return Scaffold(
  53 + appBar: AppBar(title: const Text('With controller')),
53 backgroundColor: Colors.black, 54 backgroundColor: Colors.black,
54 body: Builder( 55 body: Builder(
55 builder: (context) { 56 builder: (context) {
@@ -70,6 +70,7 @@ class _BarcodeScannerPageViewState extends State<BarcodeScannerPageView> @@ -70,6 +70,7 @@ class _BarcodeScannerPageViewState extends State<BarcodeScannerPageView>
70 @override 70 @override
71 Widget build(BuildContext context) { 71 Widget build(BuildContext context) {
72 return Scaffold( 72 return Scaffold(
  73 + appBar: AppBar(title: const Text('With PageView')),
73 backgroundColor: Colors.black, 74 backgroundColor: Colors.black,
74 body: PageView( 75 body: PageView(
75 children: [ 76 children: [
@@ -52,6 +52,7 @@ class _BarcodeScannerReturningImageState @@ -52,6 +52,7 @@ class _BarcodeScannerReturningImageState
52 @override 52 @override
53 Widget build(BuildContext context) { 53 Widget build(BuildContext context) {
54 return Scaffold( 54 return Scaffold(
  55 + appBar: AppBar(title: const Text('Returning image')),
55 body: SafeArea( 56 body: SafeArea(
56 child: Column( 57 child: Column(
57 children: [ 58 children: [
@@ -3,6 +3,8 @@ import 'dart:io'; @@ -3,6 +3,8 @@ import 'dart:io';
3 import 'package:flutter/material.dart'; 3 import 'package:flutter/material.dart';
4 import 'package:mobile_scanner/mobile_scanner.dart'; 4 import 'package:mobile_scanner/mobile_scanner.dart';
5 5
  6 +import 'package:mobile_scanner_example/scanner_error_widget.dart';
  7 +
6 class BarcodeScannerWithScanWindow extends StatefulWidget { 8 class BarcodeScannerWithScanWindow extends StatefulWidget {
7 const BarcodeScannerWithScanWindow({Key? key}) : super(key: key); 9 const BarcodeScannerWithScanWindow({Key? key}) : super(key: key);
8 10
@@ -32,6 +34,7 @@ class _BarcodeScannerWithScanWindowState @@ -32,6 +34,7 @@ class _BarcodeScannerWithScanWindowState
32 height: 200, 34 height: 200,
33 ); 35 );
34 return Scaffold( 36 return Scaffold(
  37 + appBar: AppBar(title: const Text('With Scan window')),
35 backgroundColor: Colors.black, 38 backgroundColor: Colors.black,
36 body: Builder( 39 body: Builder(
37 builder: (context) { 40 builder: (context) {
@@ -47,6 +50,9 @@ class _BarcodeScannerWithScanWindowState @@ -47,6 +50,9 @@ class _BarcodeScannerWithScanWindowState
47 this.arguments = arguments; 50 this.arguments = arguments;
48 }); 51 });
49 }, 52 },
  53 + errorBuilder: (context, error, child) {
  54 + return ScannerErrorWidget(error: error);
  55 + },
50 onDetect: onDetect, 56 onDetect: onDetect,
51 ), 57 ),
52 if (barcode != null && 58 if (barcode != null &&
@@ -18,6 +18,7 @@ class _BarcodeScannerWithoutControllerState @@ -18,6 +18,7 @@ class _BarcodeScannerWithoutControllerState
18 @override 18 @override
19 Widget build(BuildContext context) { 19 Widget build(BuildContext context) {
20 return Scaffold( 20 return Scaffold(
  21 + appBar: AppBar(title: const Text('Without controller')),
21 backgroundColor: Colors.black, 22 backgroundColor: Colors.black,
22 body: Builder( 23 body: Builder(
23 builder: (context) { 24 builder: (context) {
@@ -2,6 +2,8 @@ import 'package:flutter/material.dart'; @@ -2,6 +2,8 @@ import 'package:flutter/material.dart';
2 import 'package:image_picker/image_picker.dart'; 2 import 'package:image_picker/image_picker.dart';
3 import 'package:mobile_scanner/mobile_scanner.dart'; 3 import 'package:mobile_scanner/mobile_scanner.dart';
4 4
  5 +import 'package:mobile_scanner_example/scanner_error_widget.dart';
  6 +
5 class BarcodeScannerWithZoom extends StatefulWidget { 7 class BarcodeScannerWithZoom extends StatefulWidget {
6 const BarcodeScannerWithZoom({Key? key}) : super(key: key); 8 const BarcodeScannerWithZoom({Key? key}) : super(key: key);
7 9
@@ -23,6 +25,7 @@ class _BarcodeScannerWithZoomState extends State<BarcodeScannerWithZoom> @@ -23,6 +25,7 @@ class _BarcodeScannerWithZoomState extends State<BarcodeScannerWithZoom>
23 @override 25 @override
24 Widget build(BuildContext context) { 26 Widget build(BuildContext context) {
25 return Scaffold( 27 return Scaffold(
  28 + appBar: AppBar(title: const Text('With zoom slider')),
26 backgroundColor: Colors.black, 29 backgroundColor: Colors.black,
27 body: Builder( 30 body: Builder(
28 builder: (context) { 31 builder: (context) {
@@ -31,6 +34,9 @@ class _BarcodeScannerWithZoomState extends State<BarcodeScannerWithZoom> @@ -31,6 +34,9 @@ class _BarcodeScannerWithZoomState extends State<BarcodeScannerWithZoom>
31 MobileScanner( 34 MobileScanner(
32 controller: controller, 35 controller: controller,
33 fit: BoxFit.contain, 36 fit: BoxFit.contain,
  37 + errorBuilder: (context, error, child) {
  38 + return ScannerErrorWidget(error: error);
  39 + },
34 onDetect: (barcode) { 40 onDetect: (barcode) {
35 setState(() { 41 setState(() {
36 this.barcode = barcode; 42 this.barcode = barcode;
@@ -17,6 +17,9 @@ class ScannerErrorWidget extends StatelessWidget { @@ -17,6 +17,9 @@ class ScannerErrorWidget extends StatelessWidget {
17 case MobileScannerErrorCode.permissionDenied: 17 case MobileScannerErrorCode.permissionDenied:
18 errorMessage = 'Permission denied'; 18 errorMessage = 'Permission denied';
19 break; 19 break;
  20 + case MobileScannerErrorCode.unsupported:
  21 + errorMessage = 'Scanning is unsupported on this device';
  22 + break;
20 default: 23 default:
21 errorMessage = 'Generic Error'; 24 errorMessage = 'Generic Error';
22 break; 25 break;
@@ -8,40 +8,53 @@ @@ -8,40 +8,53 @@
8 The path provided below has to start and end with a slash "/" in order for 8 The path provided below has to start and end with a slash "/" in order for
9 it to work correctly. 9 it to work correctly.
10 10
11 - Fore more details: 11 + For more details:
12 * https://developer.mozilla.org/en-US/docs/Web/HTML/Element/base 12 * https://developer.mozilla.org/en-US/docs/Web/HTML/Element/base
  13 +
  14 + This is a placeholder for base href that will be replaced by the value of
  15 + the `--base-href` argument provided to `flutter build`.
13 --> 16 -->
14 - <base href="/"> 17 + <base href="$FLUTTER_BASE_HREF">
15 18
16 <meta charset="UTF-8"> 19 <meta charset="UTF-8">
17 <meta content="IE=Edge" http-equiv="X-UA-Compatible"> 20 <meta content="IE=Edge" http-equiv="X-UA-Compatible">
18 - <meta name="description" content="A new Flutter project."> 21 + <meta name="description" content="Demonstrates how to use the mobile_scanner plugin.">
19 22
20 <!-- iOS meta tags & icons --> 23 <!-- iOS meta tags & icons -->
21 <meta name="apple-mobile-web-app-capable" content="yes"> 24 <meta name="apple-mobile-web-app-capable" content="yes">
22 <meta name="apple-mobile-web-app-status-bar-style" content="black"> 25 <meta name="apple-mobile-web-app-status-bar-style" content="black">
23 - <meta name="apple-mobile-web-app-title" content="example"> 26 + <meta name="apple-mobile-web-app-title" content="mobile_scanner_example">
24 <link rel="apple-touch-icon" href="icons/Icon-192.png"> 27 <link rel="apple-touch-icon" href="icons/Icon-192.png">
25 28
26 <!-- Favicon --> 29 <!-- Favicon -->
27 <link rel="icon" type="image/png" href="favicon.png"/> 30 <link rel="icon" type="image/png" href="favicon.png"/>
28 31
29 - <title>example</title> 32 + <title>mobile_scanner_example</title>
30 <link rel="manifest" href="manifest.json"> 33 <link rel="manifest" href="manifest.json">
  34 +
  35 + <script>
  36 + // The value below is injected by flutter build, do not touch.
  37 + var serviceWorkerVersion = null;
  38 + </script>
  39 + <!-- This script adds the flutter initialization JS code -->
  40 + <script src="flutter.js" defer></script>
31 </head> 41 </head>
32 <body> 42 <body>
33 <script src="https://unpkg.com/@zxing/library@0.19.1" type="application/javascript"></script> 43 <script src="https://unpkg.com/@zxing/library@0.19.1" type="application/javascript"></script>
34 -  
35 - <!-- This script installs service_worker.js to provide PWA functionality to  
36 - application. For more information, see:  
37 - https://developers.google.com/web/fundamentals/primers/service-workers -->  
38 <script> 44 <script>
39 - if ('serviceWorker' in navigator) {  
40 - window.addEventListener('flutter-first-frame', function () {  
41 - navigator.serviceWorker.register('flutter_service_worker.js'); 45 + window.addEventListener('load', function(ev) {
  46 + // Download main.dart.js
  47 + _flutter.loader.loadEntrypoint({
  48 + serviceWorker: {
  49 + serviceWorkerVersion: serviceWorkerVersion,
  50 + },
  51 + onEntrypointLoaded: function(engineInitializer) {
  52 + engineInitializer.initializeEngine().then(function(appRunner) {
  53 + appRunner.runApp();
  54 + });
  55 + }
42 }); 56 });
43 - } 57 + });
44 </script> 58 </script>
45 - <script src="main.dart.js" type="application/javascript"></script>  
46 </body> 59 </body>
47 </html> 60 </html>
1 { 1 {
2 - "name": "Mobile Scanner Example", 2 + "name": "mobile_scanner_example",
3 "short_name": "mobile_scanner_example", 3 "short_name": "mobile_scanner_example",
4 "start_url": ".", 4 "start_url": ".",
5 "display": "standalone", 5 "display": "standalone",
6 "background_color": "#0175C2", 6 "background_color": "#0175C2",
7 "theme_color": "#0175C2", 7 "theme_color": "#0175C2",
8 - "description": "A barcode and qr code scanner example.", 8 + "description": "Demonstrates how to use the mobile_scanner plugin.",
9 "orientation": "portrait-primary", 9 "orientation": "portrait-primary",
10 "prefer_related_applications": false, 10 "prefer_related_applications": false,
11 "icons": [ 11 "icons": [
@@ -18,6 +18,18 @@ @@ -18,6 +18,18 @@
18 "src": "icons/Icon-512.png", 18 "src": "icons/Icon-512.png",
19 "sizes": "512x512", 19 "sizes": "512x512",
20 "type": "image/png" 20 "type": "image/png"
  21 + },
  22 + {
  23 + "src": "icons/Icon-maskable-192.png",
  24 + "sizes": "192x192",
  25 + "type": "image/png",
  26 + "purpose": "maskable"
  27 + },
  28 + {
  29 + "src": "icons/Icon-maskable-512.png",
  30 + "sizes": "512x512",
  31 + "type": "image/png",
  32 + "purpose": "maskable"
21 } 33 }
22 ] 34 ]
23 } 35 }
@@ -61,6 +61,8 @@ class MobileScannerWebPlugin { @@ -61,6 +61,8 @@ class MobileScannerWebPlugin {
61 return _torch(call.arguments); 61 return _torch(call.arguments);
62 case 'stop': 62 case 'stop':
63 return cancel(); 63 return cancel();
  64 + case 'updateScanWindow':
  65 + return Future<void>.value();
64 default: 66 default:
65 throw PlatformException( 67 throw PlatformException(
66 code: 'Unimplemented', 68 code: 'Unimplemented',
@@ -111,12 +113,14 @@ class MobileScannerWebPlugin { @@ -111,12 +113,14 @@ class MobileScannerWebPlugin {
111 .map((e) => toFormat(e)) 113 .map((e) => toFormat(e))
112 .toList(); 114 .toList();
113 } 115 }
  116 +
114 final Duration? detectionTimeout; 117 final Duration? detectionTimeout;
115 if (arguments.containsKey('timeout')) { 118 if (arguments.containsKey('timeout')) {
116 detectionTimeout = Duration(milliseconds: arguments['timeout'] as int); 119 detectionTimeout = Duration(milliseconds: arguments['timeout'] as int);
117 } else { 120 } else {
118 detectionTimeout = null; 121 detectionTimeout = null;
119 } 122 }
  123 +
120 await barCodeReader.start( 124 await barCodeReader.start(
121 cameraFacing: cameraFacing, 125 cameraFacing: cameraFacing,
122 formats: formats, 126 formats: formats,
@@ -126,20 +130,31 @@ class MobileScannerWebPlugin { @@ -126,20 +130,31 @@ class MobileScannerWebPlugin {
126 _barCodeStreamSubscription = 130 _barCodeStreamSubscription =
127 barCodeReader.detectBarcodeContinuously().listen((code) { 131 barCodeReader.detectBarcodeContinuously().listen((code) {
128 if (code != null) { 132 if (code != null) {
  133 + final List<Offset>? corners = code.corners;
  134 +
129 controller.add({ 135 controller.add({
130 'name': 'barcodeWeb', 136 'name': 'barcodeWeb',
131 'data': { 137 'data': {
132 'rawValue': code.rawValue, 138 'rawValue': code.rawValue,
133 'rawBytes': code.rawBytes, 139 'rawBytes': code.rawBytes,
134 'format': code.format.rawValue, 140 'format': code.format.rawValue,
  141 + 'displayValue': code.displayValue,
  142 + 'type': code.type.index,
  143 + if (corners != null && corners.isNotEmpty)
  144 + 'corners': corners
  145 + .map(
  146 + (Offset c) => <Object?, Object?>{'x': c.dx, 'y': c.dy},
  147 + )
  148 + .toList(),
135 }, 149 },
136 }); 150 });
137 } 151 }
138 }); 152 });
  153 +
139 final hasTorch = await barCodeReader.hasTorch(); 154 final hasTorch = await barCodeReader.hasTorch();
140 155
141 if (hasTorch && arguments.containsKey('torch')) { 156 if (hasTorch && arguments.containsKey('torch')) {
142 - barCodeReader.toggleTorch(enabled: arguments['torch'] as bool); 157 + await barCodeReader.toggleTorch(enabled: arguments['torch'] as bool);
143 } 158 }
144 159
145 return { 160 return {
@@ -148,8 +163,12 @@ class MobileScannerWebPlugin { @@ -148,8 +163,12 @@ class MobileScannerWebPlugin {
148 'videoHeight': barCodeReader.videoHeight, 163 'videoHeight': barCodeReader.videoHeight,
149 'torchable': hasTorch, 164 'torchable': hasTorch,
150 }; 165 };
151 - } catch (e) {  
152 - throw PlatformException(code: 'MobileScannerWeb', message: '$e'); 166 + } catch (e, stackTrace) {
  167 + throw PlatformException(
  168 + code: 'MobileScannerWeb',
  169 + message: '$e',
  170 + details: stackTrace.toString(),
  171 + );
153 } 172 }
154 } 173 }
155 174
@@ -9,14 +9,16 @@ Size toSize(Map data) { @@ -9,14 +9,16 @@ Size toSize(Map data) {
9 return Size(width, height); 9 return Size(width, height);
10 } 10 }
11 11
12 -List<Offset>? toCorners(List? data) {  
13 - if (data != null) {  
14 - return List.unmodifiable(  
15 - data.map((e) => Offset((e as Map)['x'] as double, e['y'] as double)),  
16 - );  
17 - } else { 12 +List<Offset>? toCorners(List<Map<Object?, Object?>>? data) {
  13 + if (data == null) {
18 return null; 14 return null;
19 } 15 }
  16 +
  17 + return List.unmodifiable(
  18 + data.map((Map<Object?, Object?> e) {
  19 + return Offset(e['x']! as double, e['y']! as double);
  20 + }),
  21 + );
20 } 22 }
21 23
22 BarcodeFormat toFormat(int value) { 24 BarcodeFormat toFormat(int value) {
@@ -11,4 +11,7 @@ enum MobileScannerErrorCode { @@ -11,4 +11,7 @@ enum MobileScannerErrorCode {
11 11
12 /// The permission to use the camera was denied. 12 /// The permission to use the camera was denied.
13 permissionDenied, 13 permissionDenied,
  14 +
  15 + /// Scanning is unsupported on the current device.
  16 + unsupported,
14 } 17 }
@@ -132,7 +132,6 @@ class _MobileScannerState extends State<MobileScanner> @@ -132,7 +132,6 @@ class _MobileScannerState extends State<MobileScanner>
132 widget.onStart?.call(arguments); 132 widget.onStart?.call(arguments);
133 widget.onScannerStarted?.call(arguments); 133 widget.onScannerStarted?.call(arguments);
134 }).catchError((error) { 134 }).catchError((error) {
135 - debugPrint('mobile_scanner: $error');  
136 if (mounted) { 135 if (mounted) {
137 setState(() { 136 setState(() {
138 _startException = error as MobileScannerException; 137 _startException = error as MobileScannerException;
@@ -207,9 +207,21 @@ class MobileScannerController { @@ -207,9 +207,21 @@ class MobileScannerController {
207 } on PlatformException catch (error) { 207 } on PlatformException catch (error) {
208 MobileScannerErrorCode errorCode = MobileScannerErrorCode.genericError; 208 MobileScannerErrorCode errorCode = MobileScannerErrorCode.genericError;
209 209
210 - if (error.code == "MobileScannerWeb") {  
211 - errorCode = MobileScannerErrorCode.permissionDenied; 210 + final String? errorMessage = error.message;
  211 +
  212 + if (kIsWeb) {
  213 + if (errorMessage == null) {
  214 + errorCode = MobileScannerErrorCode.genericError;
  215 + } else if (errorMessage.contains('NotFoundError') ||
  216 + errorMessage.contains('NotSupportedError')) {
  217 + errorCode = MobileScannerErrorCode.unsupported;
  218 + } else if (errorMessage.contains('NotAllowedError')) {
  219 + errorCode = MobileScannerErrorCode.permissionDenied;
  220 + } else {
  221 + errorCode = MobileScannerErrorCode.genericError;
  222 + }
212 } 223 }
  224 +
213 isStarting = false; 225 isStarting = false;
214 226
215 throw MobileScannerException( 227 throw MobileScannerException(
@@ -388,6 +400,10 @@ class MobileScannerController { @@ -388,6 +400,10 @@ class MobileScannerController {
388 rawValue: barcode['rawValue'] as String?, 400 rawValue: barcode['rawValue'] as String?,
389 rawBytes: barcode['rawBytes'] as Uint8List?, 401 rawBytes: barcode['rawBytes'] as Uint8List?,
390 format: toFormat(barcode['format'] as int), 402 format: toFormat(barcode['format'] as int),
  403 + corners: toCorners(
  404 + (barcode['corners'] as List<Object?>? ?? [])
  405 + .cast<Map<Object?, Object?>>(),
  406 + ),
391 ), 407 ),
392 ], 408 ],
393 ), 409 ),
@@ -92,7 +92,9 @@ class Barcode { @@ -92,7 +92,9 @@ class Barcode {
92 92
93 /// Create a [Barcode] from native data. 93 /// Create a [Barcode] from native data.
94 Barcode.fromNative(Map data) 94 Barcode.fromNative(Map data)
95 - : corners = toCorners(data['corners'] as List?), 95 + : corners = toCorners(
  96 + (data['corners'] as List?)?.cast<Map<Object?, Object?>>(),
  97 + ),
96 format = toFormat(data['format'] as int), 98 format = toFormat(data['format'] as int),
97 rawBytes = data['rawBytes'] as Uint8List?, 99 rawBytes = data['rawBytes'] as Uint8List?,
98 rawValue = data['rawValue'] as String?, 100 rawValue = data['rawValue'] as String?,
@@ -201,18 +203,20 @@ class ContactInfo { @@ -201,18 +203,20 @@ class ContactInfo {
201 /// Create a [ContactInfo] from native data. 203 /// Create a [ContactInfo] from native data.
202 ContactInfo.fromNative(Map data) 204 ContactInfo.fromNative(Map data)
203 : addresses = List.unmodifiable( 205 : addresses = List.unmodifiable(
204 - (data['addresses'] as List).map((e) => Address.fromNative(e as Map)), 206 + (data['addresses'] as List? ?? [])
  207 + .cast<Map>()
  208 + .map(Address.fromNative),
205 ), 209 ),
206 emails = List.unmodifiable( 210 emails = List.unmodifiable(
207 - (data['emails'] as List).map((e) => Email.fromNative(e as Map)), 211 + (data['emails'] as List? ?? []).cast<Map>().map(Email.fromNative),
208 ), 212 ),
209 name = toName(data['name'] as Map?), 213 name = toName(data['name'] as Map?),
210 organization = data['organization'] as String?, 214 organization = data['organization'] as String?,
211 phones = List.unmodifiable( 215 phones = List.unmodifiable(
212 - (data['phones'] as List).map((e) => Phone.fromNative(e as Map)), 216 + (data['phones'] as List? ?? []).cast<Map>().map(Phone.fromNative),
213 ), 217 ),
214 title = data['title'] as String?, 218 title = data['title'] as String?,
215 - urls = List.unmodifiable(data['urls'] as List); 219 + urls = List.unmodifiable((data['urls'] as List? ?? []).cast<String>());
216 } 220 }
217 221
218 /// An address. 222 /// An address.
@@ -227,7 +231,9 @@ class Address { @@ -227,7 +231,9 @@ class Address {
227 231
228 /// Create a [Address] from native data. 232 /// Create a [Address] from native data.
229 Address.fromNative(Map data) 233 Address.fromNative(Map data)
230 - : addressLines = List.unmodifiable(data['addressLines'] as List), 234 + : addressLines = List.unmodifiable(
  235 + (data['addressLines'] as List? ?? []).cast<String>(),
  236 + ),
231 type = AddressType.values[data['type'] as int]; 237 type = AddressType.values[data['type'] as int];
232 } 238 }
233 239
@@ -125,10 +125,8 @@ mixin InternalTorchDetection on InternalStreamCreation { @@ -125,10 +125,8 @@ mixin InternalTorchDetection on InternalStreamCreation {
125 final photoCapabilities = await promiseToFuture<PhotoCapabilities>( 125 final photoCapabilities = await promiseToFuture<PhotoCapabilities>(
126 imageCapture.getPhotoCapabilities(), 126 imageCapture.getPhotoCapabilities(),
127 ); 127 );
128 - final fillLightMode = photoCapabilities.fillLightMode;  
129 - if (fillLightMode != null) {  
130 - return fillLightMode;  
131 - } 128 +
  129 + return photoCapabilities.fillLightMode;
132 } 130 }
133 } catch (e) { 131 } catch (e) {
134 // ImageCapture is not supported by some browsers: 132 // ImageCapture is not supported by some browsers:
@@ -162,9 +160,16 @@ class Promise<T> {} @@ -162,9 +160,16 @@ class Promise<T> {}
162 160
163 @JS() 161 @JS()
164 @anonymous 162 @anonymous
165 -class PhotoCapabilities { 163 +@staticInterop
  164 +class PhotoCapabilities {}
  165 +
  166 +extension PhotoCapabilitiesExtension on PhotoCapabilities {
  167 + @JS('fillLightMode')
  168 + external List<dynamic>? get _fillLightMode;
  169 +
166 /// Returns an array of available fill light options. Options include auto, off, or flash. 170 /// Returns an array of available fill light options. Options include auto, off, or flash.
167 - external List<String>? get fillLightMode; 171 + List<String> get fillLightMode =>
  172 + _fillLightMode?.cast<String>() ?? <String>[];
168 } 173 }
169 174
170 @JS('ImageCapture') 175 @JS('ImageCapture')
1 import 'dart:async'; 1 import 'dart:async';
2 import 'dart:html'; 2 import 'dart:html';
3 import 'dart:typed_data'; 3 import 'dart:typed_data';
  4 +import 'dart:ui';
4 5
5 import 'package:js/js.dart'; 6 import 'package:js/js.dart';
6 import 'package:mobile_scanner/src/enums/camera_facing.dart'; 7 import 'package:mobile_scanner/src/enums/camera_facing.dart';
@@ -19,6 +20,16 @@ class JsZXingBrowserMultiFormatReader { @@ -19,6 +20,16 @@ class JsZXingBrowserMultiFormatReader {
19 20
20 @JS() 21 @JS()
21 @anonymous 22 @anonymous
  23 +abstract class ResultPoint {
  24 + /// The x coordinate of the point.
  25 + external double get x;
  26 +
  27 + /// The y coordinate of the point.
  28 + external double get y;
  29 +}
  30 +
  31 +@JS()
  32 +@anonymous
22 abstract class Result { 33 abstract class Result {
23 /// raw text encoded by the barcode 34 /// raw text encoded by the barcode
24 external String get text; 35 external String get text;
@@ -28,15 +39,24 @@ abstract class Result { @@ -28,15 +39,24 @@ abstract class Result {
28 39
29 /// Representing the format of the barcode that was decoded 40 /// Representing the format of the barcode that was decoded
30 external int? format; 41 external int? format;
  42 +
  43 + /// Returns the result points of the barcode. These points represent the corners of the barcode.
  44 + external List<Object?> get resultPoints;
31 } 45 }
32 46
33 extension ResultExt on Result { 47 extension ResultExt on Result {
34 Barcode toBarcode() { 48 Barcode toBarcode() {
  49 + final corners = resultPoints
  50 + .cast<ResultPoint>()
  51 + .map((ResultPoint rp) => Offset(rp.x, rp.y))
  52 + .toList();
  53 +
35 final rawBytes = this.rawBytes; 54 final rawBytes = this.rawBytes;
36 return Barcode( 55 return Barcode(
37 rawValue: text, 56 rawValue: text,
38 rawBytes: rawBytes != null ? Uint8List.fromList(rawBytes) : null, 57 rawBytes: rawBytes != null ? Uint8List.fromList(rawBytes) : null,
39 format: barcodeFormat, 58 format: barcodeFormat,
  59 + corners: corners,
40 ); 60 );
41 } 61 }
42 62