Navaron Bracke

remove the old zxing bindings

import 'dart:async';
import 'dart:html';
import 'dart:typed_data';
import 'dart:ui';
import 'package:js/js.dart';
import 'package:mobile_scanner/src/enums/barcode_format.dart';
import 'package:mobile_scanner/src/enums/camera_facing.dart';
import 'package:mobile_scanner/src/objects/barcode.dart';
import 'package:mobile_scanner/src/web/base.dart';
@JS('ZXing.BrowserMultiFormatReader')
@staticInterop
class JsZXingBrowserMultiFormatReader {
/// https://github.com/zxing-js/library/blob/1e9ccb3b6b28d75b9eef866dba196d8937eb4449/src/browser/BrowserMultiFormatReader.ts#L11
external factory JsZXingBrowserMultiFormatReader(
dynamic hints,
int timeBetweenScansMillis,
);
}
@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;
/// Returns raw bytes encoded by the barcode, if applicable, otherwise null
external Uint8ClampedList? get rawBytes;
/// 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,
);
}
/// https://github.com/zxing-js/library/blob/1e9ccb3b6b28d75b9eef866dba196d8937eb4449/src/core/BarcodeFormat.ts#L28
BarcodeFormat get barcodeFormat {
switch (format) {
case 0:
return BarcodeFormat.aztec;
case 1:
return BarcodeFormat.codebar;
case 2:
return BarcodeFormat.code39;
case 3:
return BarcodeFormat.code93;
case 4:
return BarcodeFormat.code128;
case 5:
return BarcodeFormat.dataMatrix;
case 6:
return BarcodeFormat.ean8;
case 7:
return BarcodeFormat.ean13;
case 8:
return BarcodeFormat.itf;
// case 9:
// return BarcodeFormat.maxicode;
case 10:
return BarcodeFormat.pdf417;
case 11:
return BarcodeFormat.qrCode;
// case 12:
// return BarcodeFormat.rss14;
// case 13:
// return BarcodeFormat.rssExp;
case 14:
return BarcodeFormat.upcA;
case 15:
return BarcodeFormat.upcE;
default:
return BarcodeFormat.unknown;
}
}
}
extension ZXingBarcodeFormat on BarcodeFormat {
int get zxingBarcodeFormat {
switch (this) {
case BarcodeFormat.aztec:
return 0;
case BarcodeFormat.codabar:
return 1;
case BarcodeFormat.code39:
return 2;
case BarcodeFormat.code93:
return 3;
case BarcodeFormat.code128:
return 4;
case BarcodeFormat.dataMatrix:
return 5;
case BarcodeFormat.ean8:
return 6;
case BarcodeFormat.ean13:
return 7;
case BarcodeFormat.itf:
return 8;
case BarcodeFormat.pdf417:
return 10;
case BarcodeFormat.qrCode:
return 11;
case BarcodeFormat.upcA:
return 14;
case BarcodeFormat.upcE:
return 15;
case BarcodeFormat.unknown:
case BarcodeFormat.all:
default:
return -1;
}
}
}
typedef BarcodeDetectionCallback = void Function(
Result? result,
dynamic error,
);
extension JsZXingBrowserMultiFormatReaderExt
on JsZXingBrowserMultiFormatReader {
external Promise<void> decodeFromVideoElementContinuously(
VideoElement source,
BarcodeDetectionCallback callbackFn,
);
/// Continuously decodes from video input
external void decodeContinuously(
VideoElement element,
BarcodeDetectionCallback callbackFn,
);
external Promise<void> decodeFromStream(
MediaStream stream,
VideoElement videoSource,
BarcodeDetectionCallback callbackFn,
);
external Promise<void> decodeFromConstraints(
dynamic constraints,
VideoElement videoSource,
BarcodeDetectionCallback callbackFn,
);
external void stopContinuousDecode();
external VideoElement prepareVideoElement(VideoElement videoSource);
/// Defines what the [videoElement] src will be.
external void addVideoSource(
VideoElement videoElement,
MediaStream stream,
);
external bool isVideoPlaying(VideoElement video);
external void reset();
/// The HTML video element, used to display the camera stream.
external VideoElement? videoElement;
/// The stream output from camera.
external MediaStream? stream;
}
/// Barcode reader that uses zxing-js library.
class ZXingBarcodeReader extends WebBarcodeReaderBase
with InternalStreamCreation, InternalTorchDetection {
JsZXingBrowserMultiFormatReader? _reader;
ZXingBarcodeReader({required super.videoContainer});
@override
bool get isStarted => localMediaStream != null;
@override
Future<void> start({
required CameraFacing cameraFacing,
List<BarcodeFormat>? formats,
Duration? detectionTimeout,
}) async {
final JsMap? hints;
if (formats != null && !formats.contains(BarcodeFormat.all)) {
hints = JsMap();
final zxingFormats =
formats.map((e) => e.zxingBarcodeFormat).where((e) => e > 0).toList();
// set hint DecodeHintType.POSSIBLE_FORMATS
// https://github.com/zxing-js/library/blob/1e9ccb3b6b28d75b9eef866dba196d8937eb4449/src/core/DecodeHintType.ts#L28
hints.set(2, zxingFormats);
} else {
hints = null;
}
if (detectionTimeout != null) {
frameInterval = detectionTimeout;
}
_reader = JsZXingBrowserMultiFormatReader(
hints,
frameInterval.inMilliseconds,
);
videoContainer.children = [video];
final stream = await initMediaStream(cameraFacing);
prepareVideoElement(video);
if (stream != null) {
await attachStreamToVideo(stream, video);
}
}
@override
void prepareVideoElement(VideoElement videoSource) {
_reader?.prepareVideoElement(videoSource);
}
@override
Future<void> attachStreamToVideo(
MediaStream stream,
VideoElement videoSource,
) async {
_reader?.addVideoSource(videoSource, stream);
_reader?.videoElement = videoSource;
_reader?.stream = stream;
localMediaStream = stream;
await videoSource.play();
}
StreamController<Barcode?>? controller;
@override
Future<void> stopDetectBarcodeContinuously() async {
_reader?.stopContinuousDecode();
controller?.close();
controller = null;
}
@override
Stream<Barcode?> detectBarcodeContinuously() {
controller ??= StreamController<Barcode?>();
controller!.onListen = () async {
_reader?.decodeContinuously(
video,
allowInterop((result, error) {
if (result != null) {
controller?.add(result.toBarcode());
}
}),
);
};
controller!.onCancel = () => stopDetectBarcodeContinuously();
return controller!.stream;
}
@override
Future<void> stop() async {
_reader?.reset();
super.stop();
}
}