Julian Steenbakker
Committed by GitHub

Merge pull request #679 from navaronbracke/fix_zxing_bug_on_web

fix: fix mobile scanner startup bug on web
# This file tracks properties of this Flutter project.
# Used by Flutter tool to assess capabilities and perform upgrades etc.
#
# This file should be version controlled and should not be manually edited.
# This file should be version controlled.
version:
revision: 5f105a6ca7a5ac7b8bc9b241f4c2d86f4188cf5c
revision: 796c8ef79279f9c774545b3771238c3098dbefab
channel: stable
project_type: plugin
# Tracks metadata for the flutter migrate command
migration:
platforms:
- platform: root
create_revision: 796c8ef79279f9c774545b3771238c3098dbefab
base_revision: 796c8ef79279f9c774545b3771238c3098dbefab
- platform: android
create_revision: 796c8ef79279f9c774545b3771238c3098dbefab
base_revision: 796c8ef79279f9c774545b3771238c3098dbefab
- platform: ios
create_revision: 796c8ef79279f9c774545b3771238c3098dbefab
base_revision: 796c8ef79279f9c774545b3771238c3098dbefab
- platform: macos
create_revision: 796c8ef79279f9c774545b3771238c3098dbefab
base_revision: 796c8ef79279f9c774545b3771238c3098dbefab
- platform: web
create_revision: 796c8ef79279f9c774545b3771238c3098dbefab
base_revision: 796c8ef79279f9c774545b3771238c3098dbefab
# User provided section
# List of Local paths (relative to this file) that should be
# ignored by the migrate tool.
#
# Files that are not part of the templates will be ignored by default.
unmanaged_files:
- 'lib/main.dart'
- 'ios/Runner.xcodeproj/project.pbxproj'
... ...
... ... @@ -50,6 +50,7 @@ class _BarcodeListScannerWithControllerState
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('With ValueListenableBuilder')),
backgroundColor: Colors.black,
body: Builder(
builder: (context) {
... ...
... ... @@ -50,6 +50,7 @@ class _BarcodeScannerWithControllerState
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('With controller')),
backgroundColor: Colors.black,
body: Builder(
builder: (context) {
... ...
... ... @@ -70,6 +70,7 @@ class _BarcodeScannerPageViewState extends State<BarcodeScannerPageView>
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('With PageView')),
backgroundColor: Colors.black,
body: PageView(
children: [
... ...
... ... @@ -52,6 +52,7 @@ class _BarcodeScannerReturningImageState
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Returning image')),
body: SafeArea(
child: Column(
children: [
... ...
... ... @@ -3,6 +3,8 @@ import 'dart:io';
import 'package:flutter/material.dart';
import 'package:mobile_scanner/mobile_scanner.dart';
import 'package:mobile_scanner_example/scanner_error_widget.dart';
class BarcodeScannerWithScanWindow extends StatefulWidget {
const BarcodeScannerWithScanWindow({Key? key}) : super(key: key);
... ... @@ -32,6 +34,7 @@ class _BarcodeScannerWithScanWindowState
height: 200,
);
return Scaffold(
appBar: AppBar(title: const Text('With Scan window')),
backgroundColor: Colors.black,
body: Builder(
builder: (context) {
... ... @@ -47,6 +50,9 @@ class _BarcodeScannerWithScanWindowState
this.arguments = arguments;
});
},
errorBuilder: (context, error, child) {
return ScannerErrorWidget(error: error);
},
onDetect: onDetect,
),
if (barcode != null &&
... ...
... ... @@ -18,6 +18,7 @@ class _BarcodeScannerWithoutControllerState
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Without controller')),
backgroundColor: Colors.black,
body: Builder(
builder: (context) {
... ...
... ... @@ -2,6 +2,8 @@ import 'package:flutter/material.dart';
import 'package:image_picker/image_picker.dart';
import 'package:mobile_scanner/mobile_scanner.dart';
import 'package:mobile_scanner_example/scanner_error_widget.dart';
class BarcodeScannerWithZoom extends StatefulWidget {
const BarcodeScannerWithZoom({Key? key}) : super(key: key);
... ... @@ -23,6 +25,7 @@ class _BarcodeScannerWithZoomState extends State<BarcodeScannerWithZoom>
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('With zoom slider')),
backgroundColor: Colors.black,
body: Builder(
builder: (context) {
... ... @@ -31,6 +34,9 @@ class _BarcodeScannerWithZoomState extends State<BarcodeScannerWithZoom>
MobileScanner(
controller: controller,
fit: BoxFit.contain,
errorBuilder: (context, error, child) {
return ScannerErrorWidget(error: error);
},
onDetect: (barcode) {
setState(() {
this.barcode = barcode;
... ...
... ... @@ -17,6 +17,9 @@ class ScannerErrorWidget extends StatelessWidget {
case MobileScannerErrorCode.permissionDenied:
errorMessage = 'Permission denied';
break;
case MobileScannerErrorCode.unsupported:
errorMessage = 'Scanning is unsupported on this device';
break;
default:
errorMessage = 'Generic Error';
break;
... ...
... ... @@ -8,40 +8,53 @@
The path provided below has to start and end with a slash "/" in order for
it to work correctly.
Fore more details:
For more details:
* https://developer.mozilla.org/en-US/docs/Web/HTML/Element/base
This is a placeholder for base href that will be replaced by the value of
the `--base-href` argument provided to `flutter build`.
-->
<base href="/">
<base href="$FLUTTER_BASE_HREF">
<meta charset="UTF-8">
<meta content="IE=Edge" http-equiv="X-UA-Compatible">
<meta name="description" content="A new Flutter project.">
<meta name="description" content="Demonstrates how to use the mobile_scanner plugin.">
<!-- iOS meta tags & icons -->
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black">
<meta name="apple-mobile-web-app-title" content="example">
<meta name="apple-mobile-web-app-title" content="mobile_scanner_example">
<link rel="apple-touch-icon" href="icons/Icon-192.png">
<!-- Favicon -->
<link rel="icon" type="image/png" href="favicon.png"/>
<title>example</title>
<title>mobile_scanner_example</title>
<link rel="manifest" href="manifest.json">
<script>
// The value below is injected by flutter build, do not touch.
var serviceWorkerVersion = null;
</script>
<!-- This script adds the flutter initialization JS code -->
<script src="flutter.js" defer></script>
</head>
<body>
<script src="https://unpkg.com/@zxing/library@0.19.1" type="application/javascript"></script>
<!-- This script installs service_worker.js to provide PWA functionality to
application. For more information, see:
https://developers.google.com/web/fundamentals/primers/service-workers -->
<script>
if ('serviceWorker' in navigator) {
window.addEventListener('flutter-first-frame', function () {
navigator.serviceWorker.register('flutter_service_worker.js');
window.addEventListener('load', function(ev) {
// Download main.dart.js
_flutter.loader.loadEntrypoint({
serviceWorker: {
serviceWorkerVersion: serviceWorkerVersion,
},
onEntrypointLoaded: function(engineInitializer) {
engineInitializer.initializeEngine().then(function(appRunner) {
appRunner.runApp();
});
}
});
}
});
</script>
<script src="main.dart.js" type="application/javascript"></script>
</body>
</html>
... ...
{
"name": "Mobile Scanner Example",
"name": "mobile_scanner_example",
"short_name": "mobile_scanner_example",
"start_url": ".",
"display": "standalone",
"background_color": "#0175C2",
"theme_color": "#0175C2",
"description": "A barcode and qr code scanner example.",
"description": "Demonstrates how to use the mobile_scanner plugin.",
"orientation": "portrait-primary",
"prefer_related_applications": false,
"icons": [
... ... @@ -18,6 +18,18 @@
"src": "icons/Icon-512.png",
"sizes": "512x512",
"type": "image/png"
},
{
"src": "icons/Icon-maskable-192.png",
"sizes": "192x192",
"type": "image/png",
"purpose": "maskable"
},
{
"src": "icons/Icon-maskable-512.png",
"sizes": "512x512",
"type": "image/png",
"purpose": "maskable"
}
]
}
... ...
... ... @@ -61,6 +61,8 @@ class MobileScannerWebPlugin {
return _torch(call.arguments);
case 'stop':
return cancel();
case 'updateScanWindow':
return Future<void>.value();
default:
throw PlatformException(
code: 'Unimplemented',
... ... @@ -111,12 +113,14 @@ class MobileScannerWebPlugin {
.map((e) => toFormat(e))
.toList();
}
final Duration? detectionTimeout;
if (arguments.containsKey('timeout')) {
detectionTimeout = Duration(milliseconds: arguments['timeout'] as int);
} else {
detectionTimeout = null;
}
await barCodeReader.start(
cameraFacing: cameraFacing,
formats: formats,
... ... @@ -126,20 +130,31 @@ class MobileScannerWebPlugin {
_barCodeStreamSubscription =
barCodeReader.detectBarcodeContinuously().listen((code) {
if (code != null) {
final List<Offset>? corners = code.corners;
controller.add({
'name': 'barcodeWeb',
'data': {
'rawValue': code.rawValue,
'rawBytes': code.rawBytes,
'format': code.format.rawValue,
'displayValue': code.displayValue,
'type': code.type.index,
if (corners != null && corners.isNotEmpty)
'corners': corners
.map(
(Offset c) => <Object?, Object?>{'x': c.dx, 'y': c.dy},
)
.toList(),
},
});
}
});
final hasTorch = await barCodeReader.hasTorch();
if (hasTorch && arguments.containsKey('torch')) {
barCodeReader.toggleTorch(enabled: arguments['torch'] as bool);
await barCodeReader.toggleTorch(enabled: arguments['torch'] as bool);
}
return {
... ... @@ -148,8 +163,12 @@ class MobileScannerWebPlugin {
'videoHeight': barCodeReader.videoHeight,
'torchable': hasTorch,
};
} catch (e) {
throw PlatformException(code: 'MobileScannerWeb', message: '$e');
} catch (e, stackTrace) {
throw PlatformException(
code: 'MobileScannerWeb',
message: '$e',
details: stackTrace.toString(),
);
}
}
... ...
... ... @@ -9,14 +9,16 @@ Size toSize(Map data) {
return Size(width, height);
}
List<Offset>? toCorners(List? data) {
if (data != null) {
return List.unmodifiable(
data.map((e) => Offset((e as Map)['x'] as double, e['y'] as double)),
);
} else {
List<Offset>? toCorners(List<Map<Object?, Object?>>? data) {
if (data == null) {
return null;
}
return List.unmodifiable(
data.map((Map<Object?, Object?> e) {
return Offset(e['x']! as double, e['y']! as double);
}),
);
}
BarcodeFormat toFormat(int value) {
... ...
... ... @@ -11,4 +11,7 @@ enum MobileScannerErrorCode {
/// The permission to use the camera was denied.
permissionDenied,
/// Scanning is unsupported on the current device.
unsupported,
}
... ...
... ... @@ -132,7 +132,6 @@ class _MobileScannerState extends State<MobileScanner>
widget.onStart?.call(arguments);
widget.onScannerStarted?.call(arguments);
}).catchError((error) {
debugPrint('mobile_scanner: $error');
if (mounted) {
setState(() {
_startException = error as MobileScannerException;
... ...
... ... @@ -207,9 +207,21 @@ class MobileScannerController {
} on PlatformException catch (error) {
MobileScannerErrorCode errorCode = MobileScannerErrorCode.genericError;
if (error.code == "MobileScannerWeb") {
errorCode = MobileScannerErrorCode.permissionDenied;
final String? errorMessage = error.message;
if (kIsWeb) {
if (errorMessage == null) {
errorCode = MobileScannerErrorCode.genericError;
} else if (errorMessage.contains('NotFoundError') ||
errorMessage.contains('NotSupportedError')) {
errorCode = MobileScannerErrorCode.unsupported;
} else if (errorMessage.contains('NotAllowedError')) {
errorCode = MobileScannerErrorCode.permissionDenied;
} else {
errorCode = MobileScannerErrorCode.genericError;
}
}
isStarting = false;
throw MobileScannerException(
... ... @@ -388,6 +400,10 @@ class MobileScannerController {
rawValue: barcode['rawValue'] as String?,
rawBytes: barcode['rawBytes'] as Uint8List?,
format: toFormat(barcode['format'] as int),
corners: toCorners(
(barcode['corners'] as List<Object?>? ?? [])
.cast<Map<Object?, Object?>>(),
),
),
],
),
... ...
... ... @@ -92,7 +92,9 @@ class Barcode {
/// Create a [Barcode] from native data.
Barcode.fromNative(Map data)
: corners = toCorners(data['corners'] as List?),
: corners = toCorners(
(data['corners'] as List?)?.cast<Map<Object?, Object?>>(),
),
format = toFormat(data['format'] as int),
rawBytes = data['rawBytes'] as Uint8List?,
rawValue = data['rawValue'] as String?,
... ... @@ -201,18 +203,20 @@ class ContactInfo {
/// Create a [ContactInfo] from native data.
ContactInfo.fromNative(Map data)
: addresses = List.unmodifiable(
(data['addresses'] as List).map((e) => Address.fromNative(e as Map)),
(data['addresses'] as List? ?? [])
.cast<Map>()
.map(Address.fromNative),
),
emails = List.unmodifiable(
(data['emails'] as List).map((e) => Email.fromNative(e as Map)),
(data['emails'] as List? ?? []).cast<Map>().map(Email.fromNative),
),
name = toName(data['name'] as Map?),
organization = data['organization'] as String?,
phones = List.unmodifiable(
(data['phones'] as List).map((e) => Phone.fromNative(e as Map)),
(data['phones'] as List? ?? []).cast<Map>().map(Phone.fromNative),
),
title = data['title'] as String?,
urls = List.unmodifiable(data['urls'] as List);
urls = List.unmodifiable((data['urls'] as List? ?? []).cast<String>());
}
/// An address.
... ... @@ -227,7 +231,9 @@ class Address {
/// Create a [Address] from native data.
Address.fromNative(Map data)
: addressLines = List.unmodifiable(data['addressLines'] as List),
: addressLines = List.unmodifiable(
(data['addressLines'] as List? ?? []).cast<String>(),
),
type = AddressType.values[data['type'] as int];
}
... ...
... ... @@ -125,10 +125,8 @@ mixin InternalTorchDetection on InternalStreamCreation {
final photoCapabilities = await promiseToFuture<PhotoCapabilities>(
imageCapture.getPhotoCapabilities(),
);
final fillLightMode = photoCapabilities.fillLightMode;
if (fillLightMode != null) {
return fillLightMode;
}
return photoCapabilities.fillLightMode;
}
} catch (e) {
// ImageCapture is not supported by some browsers:
... ... @@ -162,9 +160,16 @@ class Promise<T> {}
@JS()
@anonymous
class PhotoCapabilities {
@staticInterop
class PhotoCapabilities {}
extension PhotoCapabilitiesExtension on PhotoCapabilities {
@JS('fillLightMode')
external List<dynamic>? get _fillLightMode;
/// Returns an array of available fill light options. Options include auto, off, or flash.
external List<String>? get fillLightMode;
List<String> get fillLightMode =>
_fillLightMode?.cast<String>() ?? <String>[];
}
@JS('ImageCapture')
... ...
import 'dart:async';
import 'dart:html';
import 'dart:typed_data';
import 'dart:ui';
import 'package:js/js.dart';
import 'package:mobile_scanner/src/enums/camera_facing.dart';
... ... @@ -19,6 +20,16 @@ class JsZXingBrowserMultiFormatReader {
@JS()
@anonymous
abstract class ResultPoint {
/// The x coordinate of the point.
external double get x;
/// The y coordinate of the point.
external double get y;
}
@JS()
@anonymous
abstract class Result {
/// raw text encoded by the barcode
external String get text;
... ... @@ -28,15 +39,24 @@ abstract class Result {
/// Representing the format of the barcode that was decoded
external int? format;
/// Returns the result points of the barcode. These points represent the corners of the barcode.
external List<Object?> get resultPoints;
}
extension ResultExt on Result {
Barcode toBarcode() {
final corners = resultPoints
.cast<ResultPoint>()
.map((ResultPoint rp) => Offset(rp.x, rp.y))
.toList();
final rawBytes = this.rawBytes;
return Barcode(
rawValue: text,
rawBytes: rawBytes != null ? Uint8List.fromList(rawBytes) : null,
format: barcodeFormat,
corners: corners,
);
}
... ...