Julian Steenbakker
Committed by GitHub

Merge branch 'master' into master

... ... @@ -11,7 +11,7 @@ jobs:
analysis:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3.5.2
- uses: actions/checkout@v3.5.3
- uses: actions/setup-java@v3.11.0
with:
java-version: 11
... ... @@ -28,7 +28,7 @@ jobs:
formatting:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3.5.2
- uses: actions/checkout@v3.5.3
- uses: actions/setup-java@v3.11.0
with:
java-version: 11
... ...
... ... @@ -7,7 +7,7 @@
release-please:
runs-on: ubuntu-latest
steps:
- uses: GoogleCloudPlatform/release-please-action@v3.7.9
- uses: GoogleCloudPlatform/release-please-action@v3.7.10
with:
token: ${{ secrets.GITHUB_TOKEN }}
release-type: simple
... ...
# 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'
... ...
## 3.2.1
## 3.3.0
Bugs fixed:
* Fixed bug where onDetect method was being called multiple times
* [Android] Fix Gradle 8 compatibility by adding the `namespace` attribute to the build.gradle.
Improvements:
* [Android] Upgraded camera2 dependency
* Added zoomScale value notifier in MobileScannerController for the application to know the zoom scale value set actually.
The value is notified from the native SDK(CameraX/AVFoundation).
* Added resetZoomScale() in MobileScannerController to reset zoom ratio with 1x.
Both Android and iOS, if the device have ultra-wide camera, calling setZoomScale with small value causes to use ultra-wide camera and may be diffcult to detect barcodes.
resetZoomScale() is useful to use standard camera with zoom 1x.
setZoomScale() with the specific value can realize same effect, but added resetZoomScale for avoiding floating point errors.
The application can know what zoom scale value is selected actually by subscribing zoomScale above after calling resetZoomScale.
* [iOS] Call resetZoomScale while starting scan.
Android camera is initialized with a zoom of 1x, whereas iOS is initialized with the minimum zoom value, which causes to select the ultra-wide camera unintentionally ([iOS] Impossible to focus and scan the QR code due to picking the wide back camera #554).
Fixed this issue by calling resetZoomScale
* [iOS] Remove zoom animation with ramp function to match Android behavior.
## 3.2.0
Improvements:
* [iOS] Updated GoogleMLKit/BarcodeScanning to 4.0.0
... ...
... ... @@ -54,6 +54,12 @@ Ensure that you granted camera permission in XCode -> Signing & Capabilities:
<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">
## Web
This package uses ZXing on web to read barcodes so it needs to be included in `index.html` as script.
```html
<script src="https://unpkg.com/@zxing/library@0.19.1" type="application/javascript"></script>
```
## Usage
Import `package:mobile_scanner/mobile_scanner.dart`, and use the widget with or without the controller.
... ...
... ... @@ -2,14 +2,14 @@ group 'dev.steenbakker.mobile_scanner'
version '1.0-SNAPSHOT'
buildscript {
ext.kotlin_version = '1.7.22'
ext.kotlin_version = '1.8.22'
repositories {
google()
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:8.0.0'
classpath 'com.android.tools.build:gradle:8.0.2'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}
... ... @@ -56,5 +56,6 @@ dependencies {
// implementation 'com.google.android.gms:play-services-mlkit-barcode-scanning:18.1.0'
implementation 'androidx.camera:camera-camera2:1.2.2'
implementation 'androidx.camera:camera-lifecycle:1.2.2'
implementation 'androidx.camera:camera-lifecycle:1.2.3'
implementation 'androidx.camera:camera-camera2:1.2.3'
}
... ...
buildscript {
ext.kotlin_version = '1.7.22'
ext.kotlin_version = '1.8.22'
repositories {
google()
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:8.0.0'
classpath 'com.android.tools.build:gradle:8.0.2'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}
... ... @@ -26,6 +26,6 @@ subprojects {
project.evaluationDependsOn(':app')
}
task clean(type: Delete) {
tasks.register("clean", Delete) {
delete rootProject.buildDir
}
... ...
#Tue Aug 23 15:51:00 CEST 2022
#Tue Jun 27 18:47:05 CEST 2023
distributionBase=GRADLE_USER_HOME
distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-bin.zip
distributionPath=wrapper/dists
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.1.1-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
... ...
... ... @@ -204,6 +204,7 @@
files = (
);
inputPaths = (
"${TARGET_BUILD_DIR}/${INFOPLIST_PATH}",
);
name = "Thin Binary";
outputPaths = (
... ...
... ... @@ -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;
... ...
... ... @@ -3,12 +3,12 @@ description: Demonstrates how to use the mobile_scanner plugin.
publish_to: 'none' # Remove this line if you wish to publish to pub.dev
environment:
sdk: ">=2.12.0 <3.0.0"
sdk: ">=2.12.0 <4.0.0"
dependencies:
flutter:
sdk: flutter
image_picker: ^0.8.7
image_picker: ^1.0.0
mobile_scanner:
path: ../
... ...
... ... @@ -8,38 +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>
<!-- 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 src="https://unpkg.com/@zxing/library@0.19.1" type="application/javascript"></script>
<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"
}
]
}
... ...
... ... @@ -227,6 +227,8 @@ public class MobileScanner: NSObject, AVCaptureVideoDataOutputSampleBufferDelega
for output in captureSession.outputs {
captureSession.removeOutput(output)
}
latestBuffer = nil
device.removeObserver(self, forKeyPath: #keyPath(AVCaptureDevice.torchMode))
device.removeObserver(self, forKeyPath: #keyPath(AVCaptureDevice.videoZoomFactor))
registry?.unregisterTexture(textureId)
... ...
... ... @@ -8,7 +8,6 @@ import 'package:mobile_scanner/mobile_scanner_web.dart';
import 'package:mobile_scanner/src/barcode_utility.dart';
import 'package:mobile_scanner/src/enums/camera_facing.dart';
import 'package:mobile_scanner/src/objects/barcode.dart';
import 'package:mobile_scanner/src/web/utils.dart';
/// This plugin is the web implementation of mobile_scanner.
/// It only supports QR codes.
... ... @@ -26,8 +25,6 @@ class MobileScannerWebPlugin {
);
final MobileScannerWebPlugin instance = MobileScannerWebPlugin();
_jsLibrariesLoadingFuture = injectJSLibraries(barCodeReader.jsLibraries);
channel.setMethodCallHandler(instance.handleMethodCall);
event.setController(instance.controller);
}
... ... @@ -55,11 +52,8 @@ class MobileScannerWebPlugin {
ZXingBarcodeReader(videoContainer: vidDiv);
StreamSubscription? _barCodeStreamSubscription;
static late Future _jsLibrariesLoadingFuture;
/// Handle incomming messages
Future<dynamic> handleMethodCall(MethodCall call) async {
await _jsLibrariesLoadingFuture;
switch (call.method) {
case 'start':
return _start(call.arguments as Map);
... ... @@ -67,6 +61,8 @@ class MobileScannerWebPlugin {
return _torch(call.arguments);
case 'stop':
return cancel();
case 'updateScanWindow':
return Future<void>.value();
default:
throw PlatformException(
code: 'Unimplemented',
... ... @@ -117,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,
... ... @@ -132,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 {
... ... @@ -154,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;
... ... @@ -247,8 +246,8 @@ class _MobileScannerState extends State<MobileScanner>
@override
Widget build(BuildContext context) {
return LayoutBuilder(
builder: (context, constraints) {
final Size size = MediaQuery.sizeOf(context);
return ValueListenableBuilder<MobileScannerArguments?>(
valueListenable: _controller.startArguments,
builder: (context, value, child) {
... ... @@ -261,7 +260,7 @@ class _MobileScannerState extends State<MobileScanner>
widget.fit,
widget.scanWindow!,
value.size,
Size(constraints.maxWidth, constraints.maxHeight),
size,
);
_controller.updateScanWindow(scanWindow);
... ... @@ -274,10 +273,8 @@ class _MobileScannerState extends State<MobileScanner>
size: constraints.biggest,
child: FittedBox(
fit: widget.fit,
// alignment: Alignment.topCenter,
child: SizedBox(
width: value.size.width,
height: value.size.height,
child: SizedBox.fromSize(
size: value.size,
child: kIsWeb
? HtmlElementView(viewType: value.webId!)
: Texture(textureId: value.textureId!),
... ... @@ -289,8 +286,6 @@ class _MobileScannerState extends State<MobileScanner>
);
},
);
},
);
}
@override
... ...
... ... @@ -207,9 +207,21 @@ class MobileScannerController {
} on PlatformException catch (error) {
MobileScannerErrorCode errorCode = MobileScannerErrorCode.genericError;
if (error.code == "MobileScannerWeb") {
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];
}
... ...
... ... @@ -39,9 +39,6 @@ abstract class WebBarcodeReaderBase {
int get videoWidth;
int get videoHeight;
/// JS libraries to be injected into html page.
List<JsLibrary> get jsLibraries;
/// Starts streaming video
Future<void> start({
required CameraFacing cameraFacing,
... ... @@ -128,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:
... ... @@ -165,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')
... ...
... ... @@ -20,12 +20,6 @@ class Code {
external Uint8ClampedList get binaryData;
}
const jsqrLibrary = JsLibrary(
contextName: 'jsQR',
url: 'https://cdn.jsdelivr.net/npm/jsqr@1.4.0/dist/jsQR.min.js',
usesRequireJs: true,
);
/// Barcode reader that uses jsQR library.
/// jsQR supports only QR codes format.
class JsQrCodeReader extends WebBarcodeReaderBase
... ... @@ -36,9 +30,6 @@ class JsQrCodeReader extends WebBarcodeReaderBase
bool get isStarted => localMediaStream != null;
@override
List<JsLibrary> get jsLibraries => [jsqrLibrary];
@override
Future<void> start({
required CameraFacing cameraFacing,
List<BarcodeFormat>? formats,
... ...
import 'dart:async';
import 'dart:html' as html;
import 'dart:js' show JsObject, context;
import 'package:mobile_scanner/src/web/base.dart';
Future<void> loadScript(JsLibrary library) async {
dynamic amd;
dynamic define;
// ignore: avoid_dynamic_calls
if (library.usesRequireJs && context['define']?['amd'] != null) {
// In dev, requireJs is loaded in. Disable it here.
// see https://github.com/dart-lang/sdk/issues/33979
define = JsObject.fromBrowserObject(context['define'] as Object);
// ignore: avoid_dynamic_calls
amd = define['amd'];
// ignore: avoid_dynamic_calls
define['amd'] = false;
}
try {
await loadScriptUsingScriptTag(library.url);
} finally {
if (amd != null) {
// Re-enable requireJs
// ignore: avoid_dynamic_calls
define['amd'] = amd;
}
}
}
Future<void> loadScriptUsingScriptTag(String url) {
final script = html.ScriptElement()
..async = true
..defer = false
..crossOrigin = 'anonymous'
..type = 'text/javascript'
// ignore: unsafe_html
..src = url;
html.document.head!.append(script);
return script.onLoad.first;
}
/// Injects JS [libraries]
///
/// Returns a [Future] that resolves when all of the `script` tags `onLoad` events trigger.
Future<void> injectJSLibraries(List<JsLibrary> libraries) {
final List<Future<void>> loading = [];
for (final library in libraries) {
final future = loadScript(library);
loading.add(future);
}
return Future.wait(loading);
}
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,
);
}
... ... @@ -168,12 +188,6 @@ extension JsZXingBrowserMultiFormatReaderExt
external MediaStream? stream;
}
const zxingJsLibrary = JsLibrary(
contextName: 'ZXing',
url: 'https://unpkg.com/@zxing/library@0.19.1',
usesRequireJs: true,
);
/// Barcode reader that uses zxing-js library.
class ZXingBarcodeReader extends WebBarcodeReaderBase
with InternalStreamCreation, InternalTorchDetection {
... ... @@ -185,9 +199,6 @@ class ZXingBarcodeReader extends WebBarcodeReaderBase
bool get isStarted => localMediaStream != null;
@override
List<JsLibrary> get jsLibraries => [zxingJsLibrary];
@override
Future<void> start({
required CameraFacing cameraFacing,
List<BarcodeFormat>? formats,
... ...
name: mobile_scanner
description: A universal barcode and QR code scanner for Flutter based on MLKit. Uses CameraX on Android, AVFoundation on iOS and Apple Vision & AVFoundation on macOS.
version: 3.2.1
version: 3.3.0
repository: https://github.com/juliansteenbakker/mobile_scanner
environment:
sdk: ">=2.17.0 <3.0.0"
sdk: ">=2.17.0 <4.0.0"
flutter: ">=3.0.0"
dependencies:
... ... @@ -14,7 +14,6 @@ dependencies:
sdk: flutter
js: ^0.6.3
dev_dependencies:
flutter_test:
sdk: flutter
... ...