From c2e29b2f0e42c58e1b98ae38a67c4615f620d054 Mon Sep 17 00:00:00 2001 From: Navaron Bracke Date: Mon, 8 Apr 2024 09:38:23 +0200 Subject: [PATCH 01/26] bump Flutter to 3.19.0 --- CHANGELOG.md | 6 ++++-- example/pubspec.yaml | 4 ++-- pubspec.yaml | 4 ++-- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6e73bd477..0c6b73a96 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,10 @@ -## 5.0.0-beta.2 +## 5.0.0-beta.3 **BREAKING CHANGES:** -* Flutter 3.16.0 is now required. +* Flutter 3.19.0 is now required. + +## 5.0.0-beta.2 Bugs fixed: * Fixed an issue where the scan window was not updated when its size was changed. (thanks @navaronbracke !) diff --git a/example/pubspec.yaml b/example/pubspec.yaml index f2824b01c..cffa3350c 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -6,8 +6,8 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev version: 0.0.1 environment: - sdk: ">=3.2.0 <4.0.0" - flutter: ">=3.16.0" + sdk: ">=3.3.0 <4.0.0" + flutter: ">=3.19.0" # Dependencies specify other packages that your package needs in order to work. # To automatically upgrade your package dependencies to the latest versions diff --git a/pubspec.yaml b/pubspec.yaml index 51a84d543..1af89f513 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -16,8 +16,8 @@ screenshots: path: example/screenshots/overlay.png environment: - sdk: ">=3.2.0 <4.0.0" - flutter: ">=3.16.0" + sdk: ">=3.3.0 <4.0.0" + flutter: ">=3.19.0" dependencies: flutter: From caf6afa79a82f7ed5389b05f3d5bfeb914f9533c Mon Sep 17 00:00:00 2001 From: Navaron Bracke Date: Mon, 8 Apr 2024 11:55:15 +0200 Subject: [PATCH 02/26] use a regular flag on the web for pending permission request --- lib/src/web/mobile_scanner_web.dart | 40 +++++++++-------------------- 1 file changed, 12 insertions(+), 28 deletions(-) diff --git a/lib/src/web/mobile_scanner_web.dart b/lib/src/web/mobile_scanner_web.dart index 91e2ab529..8b01a24d0 100644 --- a/lib/src/web/mobile_scanner_web.dart +++ b/lib/src/web/mobile_scanner_web.dart @@ -39,17 +39,11 @@ class MobileScannerWeb extends MobileScannerPlatform { /// This container element is used by the barcode reader. HTMLDivElement? _divElement; - /// This [Completer] is used to prevent additional calls to the [start] method. + /// The flag that keeps track of whether a permission request is in progress. /// - /// To handle lifecycle changes properly, - /// the scanner is stopped when the application is inactive, - /// and restarted when the application gains focus. - /// - /// However, when the camera permission is requested, - /// the application is put in the inactive state due to the permission popup gaining focus. - /// Thus, as long as the permission status is not known, - /// any calls to the [start] method are ignored. - Completer? _cameraPermissionCompleter; + /// On the web, a permission request triggers a dialog, that in turn triggers a lifecycle change. + /// While the permission request is in progress, any attempts at (re)starting the camera should be ignored. + bool _permissionRequestInProgress = false; /// The stream controller for the media track settings stream. /// @@ -67,11 +61,6 @@ class MobileScannerWeb extends MobileScannerPlatform { MobileScannerPlatform.instance = MobileScannerWeb(); } - bool get _hasPendingPermissionRequest { - return _cameraPermissionCompleter != null && - !_cameraPermissionCompleter!.isCompleted; - } - @override Stream get barcodesStream => _barcodesController.stream; @@ -129,18 +118,15 @@ class MobileScannerWeb extends MobileScannerPlatform { } try { - // Retrieving the video track requests the camera permission. - // If the completer is not null, the permission was never requested before. - _cameraPermissionCompleter ??= Completer(); + // Retrieving the media devices requests the camera permission. + _permissionRequestInProgress = true; final MediaStream? videoStream = await window.navigator.mediaDevices .getUserMedia(constraints) .toDart as MediaStream?; // At this point the permission is granted. - if (!_cameraPermissionCompleter!.isCompleted) { - _cameraPermissionCompleter!.complete(); - } + _permissionRequestInProgress = false; if (videoStream == null) { throw const MobileScannerException( @@ -155,12 +141,6 @@ class MobileScannerWeb extends MobileScannerPlatform { return videoStream; } on DOMException catch (error, stackTrace) { - // At this point the permission request completed, although with an error, - // but the error is irrelevant for the completer. - if (!_cameraPermissionCompleter!.isCompleted) { - _cameraPermissionCompleter!.complete(); - } - final String errorMessage = error.toString(); MobileScannerErrorCode errorCode = MobileScannerErrorCode.genericError; @@ -173,6 +153,10 @@ class MobileScannerWeb extends MobileScannerPlatform { errorCode = MobileScannerErrorCode.permissionDenied; } + // At this point the permission request completed, although with an error, + // but the error is irrelevant. + _permissionRequestInProgress = false; + throw MobileScannerException( errorCode: errorCode, errorDetails: MobileScannerErrorDetails( @@ -231,7 +215,7 @@ class MobileScannerWeb extends MobileScannerPlatform { // If the permission request has not yet completed, // the camera view is not ready yet. // Prevent the permission popup from triggering a restart of the scanner. - if (_hasPendingPermissionRequest) { + if (_permissionRequestInProgress) { throw PermissionRequestPendingException(); } From 6f7ed0a6e038ee34d18d5cce22908483431a1fc0 Mon Sep 17 00:00:00 2001 From: Navaron Bracke Date: Mon, 8 Apr 2024 12:23:13 +0200 Subject: [PATCH 03/26] handle undefined facing mode on web --- lib/src/web/media_track_constraints_delegate.dart | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/lib/src/web/media_track_constraints_delegate.dart b/lib/src/web/media_track_constraints_delegate.dart index 5e34ebd3f..e3c4b2868 100644 --- a/lib/src/web/media_track_constraints_delegate.dart +++ b/lib/src/web/media_track_constraints_delegate.dart @@ -21,13 +21,20 @@ final class MediaTrackConstraintsDelegate { return null; } + final MediaTrackCapabilities capabilities = track.getCapabilities(); final MediaTrackSettings settings = track.getSettings(); + if (capabilities.facingMode.toDart.isEmpty) { + return MediaTrackSettings( + width: settings.width, + height: settings.height, + ); + } + return MediaTrackSettings( width: settings.width, height: settings.height, facingMode: settings.facingMode, - aspectRatio: settings.aspectRatio, ); } } From 3cdac6ee8a39beb00b52489acd2f4437936fbc6d Mon Sep 17 00:00:00 2001 From: Navaron Bracke Date: Mon, 8 Apr 2024 13:55:42 +0200 Subject: [PATCH 04/26] fix a typo in the podspec --- ios/mobile_scanner.podspec | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ios/mobile_scanner.podspec b/ios/mobile_scanner.podspec index 12785ac52..e6ea2b1bb 100644 --- a/ios/mobile_scanner.podspec +++ b/ios/mobile_scanner.podspec @@ -4,14 +4,14 @@ # Pod::Spec.new do |s| s.name = 'mobile_scanner' - s.version = '3.5.6' + s.version = '5.0.0' s.summary = 'An universal scanner for Flutter based on MLKit.' s.description = <<-DESC An universal scanner for Flutter based on MLKit. DESC - s.homepage = 'http://example.com' + s.homepage = 'https://github.com/juliansteenbakker/mobile_scanner' s.license = { :file => '../LICENSE' } - s.author = { 'Your Company' => 'email@example.com' } + s.author = { 'Julian Steenbakker' => 'juliansteenbakker@outlook.com' } s.source = { :path => '.' } s.source_files = 'Classes/**/*' s.dependency 'Flutter' From 3cf8f5535965488a1cdb5b61a09dc9e42c823d6b Mon Sep 17 00:00:00 2001 From: Navaron Bracke Date: Mon, 8 Apr 2024 13:59:00 +0200 Subject: [PATCH 05/26] use a single try catch block for the camera permission --- .../mobile_scanner_method_channel.dart | 54 +++++++------------ 1 file changed, 20 insertions(+), 34 deletions(-) diff --git a/lib/src/method_channel/mobile_scanner_method_channel.dart b/lib/src/method_channel/mobile_scanner_method_channel.dart index cd0174024..a9d49336c 100644 --- a/lib/src/method_channel/mobile_scanner_method_channel.dart +++ b/lib/src/method_channel/mobile_scanner_method_channel.dart @@ -94,12 +94,29 @@ class MethodChannelMobileScanner extends MobileScannerPlatform { /// /// Throws a [MobileScannerException] if the permission is not granted. Future _requestCameraPermission() async { - final MobileScannerAuthorizationState authorizationState; - try { - authorizationState = MobileScannerAuthorizationState.fromRawValue( + final MobileScannerAuthorizationState authorizationState = + MobileScannerAuthorizationState.fromRawValue( await methodChannel.invokeMethod('state') ?? 0, ); + + switch (authorizationState) { + // Authorization was already granted, no need to request it again. + case MobileScannerAuthorizationState.authorized: + return; + // Android does not have an undetermined authorization state. + // So if the permission was denied, request it again. + case MobileScannerAuthorizationState.denied: + case MobileScannerAuthorizationState.undetermined: + final bool permissionGranted = + await methodChannel.invokeMethod('request') ?? false; + + if (!permissionGranted) { + throw const MobileScannerException( + errorCode: MobileScannerErrorCode.permissionDenied, + ); + } + } } on PlatformException catch (error) { // If the permission state is invalid, that is an error. throw MobileScannerException( @@ -111,37 +128,6 @@ class MethodChannelMobileScanner extends MobileScannerPlatform { ), ); } - - switch (authorizationState) { - case MobileScannerAuthorizationState.authorized: - return; // Already authorized. - // Android does not have an undetermined authorization state. - // So if the permission was denied, request it again. - case MobileScannerAuthorizationState.denied: - case MobileScannerAuthorizationState.undetermined: - try { - final bool granted = - await methodChannel.invokeMethod('request') ?? false; - - if (granted) { - return; // Authorization was granted. - } - - throw const MobileScannerException( - errorCode: MobileScannerErrorCode.permissionDenied, - ); - } on PlatformException catch (error) { - // If the permission state is invalid, that is an error. - throw MobileScannerException( - errorCode: MobileScannerErrorCode.genericError, - errorDetails: MobileScannerErrorDetails( - code: error.code, - details: error.details as Object?, - message: error.message, - ), - ); - } - } } @override From ec7d69f6997b9ec4045a381bfa9c5c9c913eb09f Mon Sep 17 00:00:00 2001 From: Navaron Bracke Date: Mon, 8 Apr 2024 16:39:57 +0200 Subject: [PATCH 06/26] fix bug with permissions on Android --- lib/src/mobile_scanner_controller.dart | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/lib/src/mobile_scanner_controller.dart b/lib/src/mobile_scanner_controller.dart index af7297ca8..bfa221d3f 100644 --- a/lib/src/mobile_scanner_controller.dart +++ b/lib/src/mobile_scanner_controller.dart @@ -223,12 +223,14 @@ class MobileScannerController extends ValueNotifier { } /// Start scanning for barcodes. - /// Upon calling this method, the necessary camera permission will be requested. /// /// The [cameraDirection] can be used to specify the camera direction. /// If this is null, this defaults to the [facing] value. /// /// Does nothing if the camera is already running. + /// Upon calling this method, the necessary camera permission will be requested. + /// + /// If the permission is denied on iOS, MacOS or Web, there is no way to request it again. Future start({CameraFacing? cameraDirection}) async { if (_isDisposed) { throw const MobileScannerException( @@ -240,6 +242,13 @@ class MobileScannerController extends ValueNotifier { ); } + // Permission was denied, do nothing. + // When the controller is stopped, + // the error is reset so the permission can be requested again if possible. + if (value.error?.errorCode == MobileScannerErrorCode.permissionDenied) { + return; + } + // Do nothing if the camera is already running. if (value.isRunning) { return; From 3022b318e00d711e1105a69d176b757e79dd3808 Mon Sep 17 00:00:00 2001 From: Navaron Bracke Date: Tue, 9 Apr 2024 10:54:36 +0200 Subject: [PATCH 07/26] bump web to 0.5.1 --- pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pubspec.yaml b/pubspec.yaml index 1af89f513..449055b44 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -25,7 +25,7 @@ dependencies: flutter_web_plugins: sdk: flutter plugin_platform_interface: ^2.0.2 - web: ^0.4.0 + web: ^0.5.1 dev_dependencies: flutter_test: From d960662215c0a5acdbb9d457ddb8f9f2bd28929b Mon Sep 17 00:00:00 2001 From: Navaron Bracke Date: Tue, 9 Apr 2024 11:07:48 +0200 Subject: [PATCH 08/26] remove some redundant casts --- lib/src/web/media_track_constraints_delegate.dart | 8 ++------ lib/src/web/zxing/zxing_barcode_reader.dart | 6 +++--- 2 files changed, 5 insertions(+), 9 deletions(-) diff --git a/lib/src/web/media_track_constraints_delegate.dart b/lib/src/web/media_track_constraints_delegate.dart index e3c4b2868..8c5749200 100644 --- a/lib/src/web/media_track_constraints_delegate.dart +++ b/lib/src/web/media_track_constraints_delegate.dart @@ -9,17 +9,13 @@ final class MediaTrackConstraintsDelegate { /// Get the settings for the given [mediaStream]. MediaTrackSettings? getSettings(MediaStream? mediaStream) { - final List? tracks = mediaStream?.getVideoTracks().toDart; + final List? tracks = mediaStream?.getVideoTracks().toDart; if (tracks == null || tracks.isEmpty) { return null; } - final MediaStreamTrack? track = tracks.first as MediaStreamTrack?; - - if (track == null) { - return null; - } + final MediaStreamTrack track = tracks.first; final MediaTrackCapabilities capabilities = track.getCapabilities(); final MediaTrackSettings settings = track.getSettings(); diff --git a/lib/src/web/zxing/zxing_barcode_reader.dart b/lib/src/web/zxing/zxing_barcode_reader.dart index d56e9d96d..5a4fbb140 100644 --- a/lib/src/web/zxing/zxing_barcode_reader.dart +++ b/lib/src/web/zxing/zxing_barcode_reader.dart @@ -115,8 +115,8 @@ final class ZXingBarcodeReader extends BarcodeReader { ) async { final JSPromise? result = _reader?.attachStreamToVideo.callAsFunction( _reader as JSAny?, - videoStream as JSAny, - videoElement as JSAny, + videoStream, + videoElement, ) as JSPromise?; await result?.toDart; @@ -136,7 +136,7 @@ final class ZXingBarcodeReader extends BarcodeReader { controller.onListen = () { _reader?.decodeContinuously.callAsFunction( _reader as JSAny?, - _reader?.videoElement as JSAny?, + _reader?.videoElement, (Result? result, JSAny? error) { if (controller.isClosed || result == null) { return; From df286f97b4916a1b235bfc753dac322ccffe93fe Mon Sep 17 00:00:00 2001 From: Navaron Bracke Date: Tue, 9 Apr 2024 11:12:40 +0200 Subject: [PATCH 09/26] use the HTMLScriptElement constructor --- lib/src/web/barcode_reader.dart | 27 +++++++++++++-------------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/lib/src/web/barcode_reader.dart b/lib/src/web/barcode_reader.dart index f301af426..42fc894ba 100644 --- a/lib/src/web/barcode_reader.dart +++ b/lib/src/web/barcode_reader.dart @@ -62,20 +62,19 @@ abstract class BarcodeReader { final Completer completer = Completer(); - final HTMLScriptElement script = - (document.createElement('script') as HTMLScriptElement) - ..id = scriptId - ..async = true - ..defer = false - ..type = 'application/javascript' - ..lang = 'javascript' - ..crossOrigin = 'anonymous' - ..src = alternateScriptUrl ?? scriptUrl - ..onload = (JSAny _) { - if (!completer.isCompleted) { - completer.complete(); - } - }.toJS; + final HTMLScriptElement script = HTMLScriptElement() + ..id = scriptId + ..async = true + ..defer = false + ..type = 'application/javascript' + ..lang = 'javascript' + ..crossOrigin = 'anonymous' + ..src = alternateScriptUrl ?? scriptUrl + ..onload = (JSAny _) { + if (!completer.isCompleted) { + completer.complete(); + } + }.toJS; script.onerror = (JSAny _) { if (!completer.isCompleted) { From 31fcad59350d9e960f87b311bd443cde7e990624 Mon Sep 17 00:00:00 2001 From: Navaron Bracke Date: Tue, 9 Apr 2024 11:21:31 +0200 Subject: [PATCH 10/26] remove cast on media stream --- lib/src/web/mobile_scanner_web.dart | 16 ++-------------- 1 file changed, 2 insertions(+), 14 deletions(-) diff --git a/lib/src/web/mobile_scanner_web.dart b/lib/src/web/mobile_scanner_web.dart index 8b01a24d0..a64e4c175 100644 --- a/lib/src/web/mobile_scanner_web.dart +++ b/lib/src/web/mobile_scanner_web.dart @@ -121,24 +121,12 @@ class MobileScannerWeb extends MobileScannerPlatform { // Retrieving the media devices requests the camera permission. _permissionRequestInProgress = true; - final MediaStream? videoStream = await window.navigator.mediaDevices - .getUserMedia(constraints) - .toDart as MediaStream?; + final MediaStream videoStream = + await window.navigator.mediaDevices.getUserMedia(constraints).toDart; // At this point the permission is granted. _permissionRequestInProgress = false; - if (videoStream == null) { - throw const MobileScannerException( - errorCode: MobileScannerErrorCode.genericError, - errorDetails: MobileScannerErrorDetails( - message: - 'Could not create a video stream from the camera with the given options. ' - 'The browser might not support the given constraints.', - ), - ); - } - return videoStream; } on DOMException catch (error, stackTrace) { final String errorMessage = error.toString(); From 1c7ebe264c1949aa9e33a9167ccbadba47af4810 Mon Sep 17 00:00:00 2001 From: Navaron Bracke Date: Tue, 9 Apr 2024 11:23:41 +0200 Subject: [PATCH 11/26] remove additional JSAny cast --- lib/src/web/mobile_scanner_web.dart | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/lib/src/web/mobile_scanner_web.dart b/lib/src/web/mobile_scanner_web.dart index a64e4c175..0f8d5606d 100644 --- a/lib/src/web/mobile_scanner_web.dart +++ b/lib/src/web/mobile_scanner_web.dart @@ -89,7 +89,7 @@ class MobileScannerWeb extends MobileScannerPlatform { Future _prepareVideoStream( CameraFacing cameraDirection, ) async { - if ((window.navigator.mediaDevices as JSAny?).isUndefinedOrNull) { + if (window.navigator.mediaDevices.isUndefinedOrNull) { throw const MobileScannerException( errorCode: MobileScannerErrorCode.unsupported, errorDetails: MobileScannerErrorDetails( @@ -104,7 +104,7 @@ class MobileScannerWeb extends MobileScannerPlatform { final MediaStreamConstraints constraints; - if ((capabilities as JSAny).isUndefinedOrNull || !capabilities.facingMode) { + if (capabilities.isUndefinedOrNull || !capabilities.facingMode) { constraints = MediaStreamConstraints(video: true.toJS); } else { final String facingMode = switch (cameraDirection) { @@ -113,7 +113,9 @@ class MobileScannerWeb extends MobileScannerPlatform { }; constraints = MediaStreamConstraints( - video: MediaTrackConstraintSet(facingMode: facingMode.toJS) as JSAny, + video: MediaTrackConstraintSet( + facingMode: facingMode.toJS, + ), ); } From 71de394990741ca402f7cf5279dd5ab4567a3c89 Mon Sep 17 00:00:00 2001 From: Navaron Bracke Date: Tue, 9 Apr 2024 11:43:18 +0200 Subject: [PATCH 12/26] remove redundant cleanup code --- lib/src/web/mobile_scanner_web.dart | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/lib/src/web/mobile_scanner_web.dart b/lib/src/web/mobile_scanner_web.dart index 0f8d5606d..12ec4c406 100644 --- a/lib/src/web/mobile_scanner_web.dart +++ b/lib/src/web/mobile_scanner_web.dart @@ -339,24 +339,5 @@ class MobileScannerWeb extends MobileScannerPlatform { await stop(); await _barcodesController.close(); await _settingsController.close(); - - // Finally, remove the video element from the DOM. - try { - final HTMLCollection? divChildren = _divElement?.children; - - // Since the exact element is unknown, remove all children. - // In practice, there should only be one child, the single video element. - if (divChildren != null && divChildren.length > 0) { - for (int i = 0; i < divChildren.length; i++) { - final Node? child = divChildren.item(i); - - if (child != null) { - _divElement?.removeChild(child); - } - } - } - } catch (_) { - // The video element was no longer a child of the container element. - } } } From ed2b15e446ef55441167014b82b08a2c3f00ddfa Mon Sep 17 00:00:00 2001 From: Navaron Bracke Date: Tue, 9 Apr 2024 12:08:36 +0200 Subject: [PATCH 13/26] rename the web view type to kebab case --- lib/src/web/mobile_scanner_web.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/src/web/mobile_scanner_web.dart b/lib/src/web/mobile_scanner_web.dart index 12ec4c406..5d898c4fc 100644 --- a/lib/src/web/mobile_scanner_web.dart +++ b/lib/src/web/mobile_scanner_web.dart @@ -55,7 +55,7 @@ class MobileScannerWeb extends MobileScannerPlatform { StreamController.broadcast(); /// The view type for the platform view factory. - static const String _viewType = 'MobileScannerWeb'; + static const String _viewType = 'mobile-scanner-view'; static void registerWith(Registrar registrar) { MobileScannerPlatform.instance = MobileScannerWeb(); From d1325538a58f37585448c0b4f81ef861c5e25bef Mon Sep 17 00:00:00 2001 From: Navaron Bracke Date: Tue, 9 Apr 2024 15:11:12 +0200 Subject: [PATCH 14/26] fix frozen video stream on second try on web --- lib/src/web/mobile_scanner_web.dart | 95 ++++++++++++++++++++--------- 1 file changed, 67 insertions(+), 28 deletions(-) diff --git a/lib/src/web/mobile_scanner_web.dart b/lib/src/web/mobile_scanner_web.dart index 5d898c4fc..0ec5a5631 100644 --- a/lib/src/web/mobile_scanner_web.dart +++ b/lib/src/web/mobile_scanner_web.dart @@ -35,9 +35,7 @@ class MobileScannerWeb extends MobileScannerPlatform { StreamSubscription? _barcodesSubscription; /// The container div element for the camera view. - /// - /// This container element is used by the barcode reader. - HTMLDivElement? _divElement; + late HTMLDivElement _divElement; /// The flag that keeps track of whether a permission request is in progress. /// @@ -54,8 +52,14 @@ class MobileScannerWeb extends MobileScannerPlatform { final StreamController _settingsController = StreamController.broadcast(); - /// The view type for the platform view factory. - static const String _viewType = 'mobile-scanner-view'; + /// The texture ID for the camera view. + int _textureId = 1; + + /// The video element for the camera view. + late HTMLVideoElement _videoElement; + + /// Get the view type for the platform view factory. + String _getViewType(int textureId) => 'mobile-scanner-view-$textureId'; static void registerWith(Registrar registrar) { MobileScannerPlatform.instance = MobileScannerWeb(); @@ -72,6 +76,33 @@ class MobileScannerWeb extends MobileScannerPlatform { Stream get zoomScaleStateStream => _settingsController.stream.map((_) => 1.0); + /// Create the [HTMLVideoElement] along with its parent container [HTMLDivElement]. + HTMLVideoElement _createVideoElement(int textureId) { + final HTMLVideoElement videoElement = HTMLVideoElement(); + + videoElement.style + ..height = '100%' + ..width = '100%' + ..objectFit = 'cover' + ..transformOrigin = 'center' + ..pointerEvents = 'none'; + + // Attach the video element to its parent container + // and setup the PlatformView factory for this `textureId`. + _divElement = HTMLDivElement() + ..style.objectFit = 'cover' + ..style.height = '100%' + ..style.width = '100%' + ..append(videoElement); + + ui_web.platformViewRegistry.registerViewFactory( + _getViewType(textureId), + (_) => _divElement, + ); + + return videoElement; + } + void _handleMediaTrackSettingsChange(MediaTrackSettings settings) { if (_settingsController.isClosed) { return; @@ -80,6 +111,32 @@ class MobileScannerWeb extends MobileScannerPlatform { _settingsController.add(settings); } + /// Flip the [videoElement] horizontally, + /// if the [videoStream] indicates that is facing the user. + void _maybeFlipVideoPreview( + HTMLVideoElement videoElement, + MediaStream videoStream, + ) { + final List tracks = videoStream.getVideoTracks().toDart; + + if (tracks.isEmpty) { + return; + } + + final MediaStreamTrack videoTrack = tracks.first; + final MediaTrackCapabilities capabilities = videoTrack.getCapabilities(); + + // TODO: this is empty on MacOS, where there is no facing mode, but one, user facing camera. + // Facing mode is not supported by this track, do nothing. + if (capabilities.facingMode.toDart.isEmpty) { + return; + } + + if (videoTrack.getSettings().facingMode == 'user') { + videoElement.style.transform = 'scaleX(-1)'; + } + } + /// Prepare a [MediaStream] for the video output. /// /// This method requests permission to use the camera. @@ -168,7 +225,7 @@ class MobileScannerWeb extends MobileScannerPlatform { return const SizedBox(); } - return const HtmlElementView(viewType: _viewType); + return HtmlElementView(viewType: _getViewType(_textureId)); } @override @@ -213,18 +270,6 @@ class MobileScannerWeb extends MobileScannerPlatform { alternateScriptUrl: _alternateScriptUrl, ); - // Setup the view factory & container element. - if (_divElement == null) { - _divElement = (document.createElement('div') as HTMLDivElement) - ..style.width = '100%' - ..style.height = '100%'; - - ui_web.platformViewRegistry.registerViewFactory( - _viewType, - (int id) => _divElement!, - ); - } - if (_barcodeReader.isScanning) { throw const MobileScannerException( errorCode: MobileScannerErrorCode.controllerAlreadyInitialized, @@ -251,21 +296,15 @@ class MobileScannerWeb extends MobileScannerPlatform { _handleMediaTrackSettingsChange, ); - final HTMLVideoElement videoElement; + _textureId += 1; // Request a new texture. - // Attach the video element to the DOM, through its parent container. - // If a video element is already present, reuse it. - if (_divElement!.children.length == 0) { - videoElement = document.createElement('video') as HTMLVideoElement; + _videoElement = _createVideoElement(_textureId); - _divElement!.appendChild(videoElement); - } else { - videoElement = _divElement!.children.item(0)! as HTMLVideoElement; - } + _maybeFlipVideoPreview(_videoElement, videoStream); await _barcodeReader.start( startOptions, - videoElement: videoElement, + videoElement: _videoElement, videoStream: videoStream, ); } catch (error, stackTrace) { From cd6d5bd4cc1c454f8554bc89b4695f0cfc7c50da Mon Sep 17 00:00:00 2001 From: Navaron Bracke Date: Tue, 9 Apr 2024 16:15:49 +0200 Subject: [PATCH 15/26] lazily set up the barcode reader on web --- lib/src/web/mobile_scanner_web.dart | 29 ++++++++++++++++------------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/lib/src/web/mobile_scanner_web.dart b/lib/src/web/mobile_scanner_web.dart index 0ec5a5631..881743d39 100644 --- a/lib/src/web/mobile_scanner_web.dart +++ b/lib/src/web/mobile_scanner_web.dart @@ -25,7 +25,7 @@ class MobileScannerWeb extends MobileScannerPlatform { String? _alternateScriptUrl; /// The internal barcode reader. - final BarcodeReader _barcodeReader = ZXingBarcodeReader(); + BarcodeReader? _barcodeReader; /// The stream controller for the barcode stream. final StreamController _barcodesController = @@ -221,11 +221,11 @@ class MobileScannerWeb extends MobileScannerPlatform { @override Widget buildCameraView() { - if (!_barcodeReader.isScanning) { - return const SizedBox(); + if (_barcodeReader?.isScanning ?? false) { + return HtmlElementView(viewType: _getViewType(_textureId)); } - return HtmlElementView(viewType: _getViewType(_textureId)); + return const SizedBox(); } @override @@ -266,11 +266,13 @@ class MobileScannerWeb extends MobileScannerPlatform { throw PermissionRequestPendingException(); } - await _barcodeReader.maybeLoadLibrary( + _barcodeReader = ZXingBarcodeReader(); + + await _barcodeReader?.maybeLoadLibrary( alternateScriptUrl: _alternateScriptUrl, ); - if (_barcodeReader.isScanning) { + if (_barcodeReader?.isScanning ?? false) { throw const MobileScannerException( errorCode: MobileScannerErrorCode.controllerAlreadyInitialized, errorDetails: MobileScannerErrorDetails( @@ -292,7 +294,7 @@ class MobileScannerWeb extends MobileScannerPlatform { } // Listen for changes to the media track settings. - _barcodeReader.setMediaTrackSettingsListener( + _barcodeReader?.setMediaTrackSettingsListener( _handleMediaTrackSettingsChange, ); @@ -302,7 +304,7 @@ class MobileScannerWeb extends MobileScannerPlatform { _maybeFlipVideoPreview(_videoElement, videoStream); - await _barcodeReader.start( + await _barcodeReader?.start( startOptions, videoElement: _videoElement, videoStream: videoStream, @@ -318,7 +320,7 @@ class MobileScannerWeb extends MobileScannerPlatform { } try { - _barcodesSubscription = _barcodeReader.detectBarcodes().listen( + _barcodesSubscription = _barcodeReader?.detectBarcodes().listen( (BarcodeCapture barcode) { if (_barcodesController.isClosed) { return; @@ -328,15 +330,15 @@ class MobileScannerWeb extends MobileScannerPlatform { }, ); - final bool hasTorch = await _barcodeReader.hasTorch(); + final bool hasTorch = await _barcodeReader?.hasTorch() ?? false; if (hasTorch && startOptions.torchEnabled) { - await _barcodeReader.setTorchState(TorchState.on); + await _barcodeReader?.setTorchState(TorchState.on); } return MobileScannerViewAttributes( hasTorch: hasTorch, - size: _barcodeReader.videoSize, + size: _barcodeReader?.videoSize ?? Size.zero, ); } catch (error, stackTrace) { throw MobileScannerException( @@ -359,7 +361,8 @@ class MobileScannerWeb extends MobileScannerPlatform { await _barcodesSubscription?.cancel(); _barcodesSubscription = null; - await _barcodeReader.stop(); + await _barcodeReader?.stop(); + _barcodeReader = null; } @override From 320e648c5af8fff89a70165d3de8239eb137a711 Mon Sep 17 00:00:00 2001 From: Navaron Bracke Date: Tue, 9 Apr 2024 16:37:31 +0200 Subject: [PATCH 16/26] do not dispose global controller on web --- lib/src/web/mobile_scanner_web.dart | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/lib/src/web/mobile_scanner_web.dart b/lib/src/web/mobile_scanner_web.dart index 881743d39..ab77313f3 100644 --- a/lib/src/web/mobile_scanner_web.dart +++ b/lib/src/web/mobile_scanner_web.dart @@ -353,10 +353,6 @@ class MobileScannerWeb extends MobileScannerPlatform { @override Future stop() async { - if (_barcodesController.isClosed) { - return; - } - // Ensure the barcode scanner is stopped, by cancelling the subscription. await _barcodesSubscription?.cancel(); _barcodesSubscription = null; @@ -374,12 +370,8 @@ class MobileScannerWeb extends MobileScannerPlatform { @override Future dispose() async { - if (_barcodesController.isClosed) { - return; - } - + // The `_barcodesController` and `_settingsController` + // are not closed, as these have the same lifetime as the plugin. await stop(); - await _barcodesController.close(); - await _settingsController.close(); } } From 2c9c59b9112655bbad9d0360a0b6e82c1adeb715 Mon Sep 17 00:00:00 2001 From: Navaron Bracke Date: Tue, 9 Apr 2024 16:39:30 +0200 Subject: [PATCH 17/26] add changelog entry for bugfix --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0c6b73a96..2c484d29f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,9 @@ * Flutter 3.19.0 is now required. +Bugs fixed: +* Fixed an issue where the camera preview and barcode scanner did not work the second time on web. + ## 5.0.0-beta.2 Bugs fixed: From 5c9f0098a4f2b9a09a0cf7f2fa880a29e0ad19a1 Mon Sep 17 00:00:00 2001 From: Navaron Bracke Date: Wed, 10 Apr 2024 08:37:03 +0200 Subject: [PATCH 18/26] bump iOS minimum to iOS 12 --- CHANGELOG.md | 1 + example/ios/Flutter/AppFrameworkInfo.plist | 2 +- example/ios/Podfile | 4 ++-- example/ios/Runner.xcodeproj/project.pbxproj | 6 +++--- ios/mobile_scanner.podspec | 2 +- 5 files changed, 8 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2c484d29f..2b7907c6b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ **BREAKING CHANGES:** * Flutter 3.19.0 is now required. +* [iOS] iOS 12.0 is now the minimum supported iOS version. Bugs fixed: * Fixed an issue where the camera preview and barcode scanner did not work the second time on web. diff --git a/example/ios/Flutter/AppFrameworkInfo.plist b/example/ios/Flutter/AppFrameworkInfo.plist index 9625e105d..7c5696400 100644 --- a/example/ios/Flutter/AppFrameworkInfo.plist +++ b/example/ios/Flutter/AppFrameworkInfo.plist @@ -21,6 +21,6 @@ CFBundleVersion 1.0 MinimumOSVersion - 11.0 + 12.0 diff --git a/example/ios/Podfile b/example/ios/Podfile index 1dfb86970..5905f045b 100644 --- a/example/ios/Podfile +++ b/example/ios/Podfile @@ -1,5 +1,5 @@ # Uncomment this line to define a global platform for your project -# platform :ios, '11.0' +# platform :ios, '12.0' # CocoaPods analytics sends network stats synchronously affecting flutter build latency. ENV['COCOAPODS_DISABLE_STATS'] = 'true' @@ -41,7 +41,7 @@ post_install do |installer| installer.pods_project.targets.each do |target| flutter_additional_ios_build_settings(target) target.build_configurations.each do |config| - config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '11.0' + config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '12.0' end end end diff --git a/example/ios/Runner.xcodeproj/project.pbxproj b/example/ios/Runner.xcodeproj/project.pbxproj index da346373e..f3ebff98f 100644 --- a/example/ios/Runner.xcodeproj/project.pbxproj +++ b/example/ios/Runner.xcodeproj/project.pbxproj @@ -452,7 +452,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 11.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; @@ -583,7 +583,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 11.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -632,7 +632,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 11.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; diff --git a/ios/mobile_scanner.podspec b/ios/mobile_scanner.podspec index e6ea2b1bb..437a3d030 100644 --- a/ios/mobile_scanner.podspec +++ b/ios/mobile_scanner.podspec @@ -16,7 +16,7 @@ An universal scanner for Flutter based on MLKit. s.source_files = 'Classes/**/*' s.dependency 'Flutter' s.dependency 'GoogleMLKit/BarcodeScanning', '~> 4.0.0' - s.platform = :ios, '11.0' + s.platform = :ios, '12.0' s.static_framework = true # Flutter.framework does not contain a i386 slice. s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES', 'EXCLUDED_ARCHS[sdk=iphonesimulator*]' => 'i386' } From 8d162c1e563232a73d2ff436e215c8f5d52d1162 Mon Sep 17 00:00:00 2001 From: Navaron Bracke Date: Wed, 10 Apr 2024 08:45:30 +0200 Subject: [PATCH 19/26] bump GoogleMLKit/BarcodeScanning to 5.0.0 --- ios/mobile_scanner.podspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ios/mobile_scanner.podspec b/ios/mobile_scanner.podspec index 437a3d030..e423e0a51 100644 --- a/ios/mobile_scanner.podspec +++ b/ios/mobile_scanner.podspec @@ -15,7 +15,7 @@ An universal scanner for Flutter based on MLKit. s.source = { :path => '.' } s.source_files = 'Classes/**/*' s.dependency 'Flutter' - s.dependency 'GoogleMLKit/BarcodeScanning', '~> 4.0.0' + s.dependency 'GoogleMLKit/BarcodeScanning', '~> 5.0.0' s.platform = :ios, '12.0' s.static_framework = true # Flutter.framework does not contain a i386 slice. From 8dbf1c15abf8fe1aa7fbf3a327ab323e4980a1a6 Mon Sep 17 00:00:00 2001 From: Navaron Bracke Date: Wed, 10 Apr 2024 09:24:52 +0200 Subject: [PATCH 20/26] add iOS Privacy Manifest --- CHANGELOG.md | 1 + ios/Resources/PrivacyInfo.xcprivacy | 14 ++++++++++++++ ios/mobile_scanner.podspec | 1 + 3 files changed, 16 insertions(+) create mode 100644 ios/Resources/PrivacyInfo.xcprivacy diff --git a/CHANGELOG.md b/CHANGELOG.md index 2b7907c6b..7eae37e5c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ * Flutter 3.19.0 is now required. * [iOS] iOS 12.0 is now the minimum supported iOS version. +* [iOS] Adds a Privacy Manifest. Bugs fixed: * Fixed an issue where the camera preview and barcode scanner did not work the second time on web. diff --git a/ios/Resources/PrivacyInfo.xcprivacy b/ios/Resources/PrivacyInfo.xcprivacy new file mode 100644 index 000000000..a1f9119d1 --- /dev/null +++ b/ios/Resources/PrivacyInfo.xcprivacy @@ -0,0 +1,14 @@ + + + + + NSPrivacyAccessedAPITypes + + NSPrivacyCollectedDataTypes + + NSPrivacyTrackingDomains + + NSPrivacyTracking + + + diff --git a/ios/mobile_scanner.podspec b/ios/mobile_scanner.podspec index e423e0a51..c0522bec2 100644 --- a/ios/mobile_scanner.podspec +++ b/ios/mobile_scanner.podspec @@ -21,4 +21,5 @@ An universal scanner for Flutter based on MLKit. # Flutter.framework does not contain a i386 slice. s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES', 'EXCLUDED_ARCHS[sdk=iphonesimulator*]' => 'i386' } s.swift_version = '5.0' + s.resource_bundles = { 'mobile_scanner_privacy' => ['Resources/PrivacyInfo.xcprivacy'] } end From 6490aabcd49ccddea173b9493ca65c2bba2a8266 Mon Sep 17 00:00:00 2001 From: Navaron Bracke Date: Wed, 10 Apr 2024 09:28:14 +0200 Subject: [PATCH 21/26] fix typo in MacOS podspec --- macos/mobile_scanner.podspec | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/macos/mobile_scanner.podspec b/macos/mobile_scanner.podspec index 55d9f192e..5858536e4 100644 --- a/macos/mobile_scanner.podspec +++ b/macos/mobile_scanner.podspec @@ -4,14 +4,14 @@ # Pod::Spec.new do |s| s.name = 'mobile_scanner' - s.version = '3.5.6' + s.version = '5.0.0' s.summary = 'An universal scanner for Flutter based on MLKit.' s.description = <<-DESC An universal scanner for Flutter based on MLKit. DESC - s.homepage = 'http://example.com' + s.homepage = 'https://github.com/juliansteenbakker/mobile_scanner' s.license = { :file => '../LICENSE' } - s.author = { 'Your Company' => 'email@example.com' } + s.author = { 'Julian Steenbakker' => 'juliansteenbakker@outlook.com' } s.source = { :path => '.' } s.source_files = 'Classes/**/*' s.dependency 'FlutterMacOS' From fde4fc12b7fe559b016d5c15cb71293ec974b4be Mon Sep 17 00:00:00 2001 From: Navaron Bracke Date: Tue, 16 Apr 2024 15:00:56 +0200 Subject: [PATCH 22/26] fix Gradle warning about deprecated apply script method --- example/android/app/build.gradle | 4 ---- example/android/build.gradle | 13 ------------- example/android/settings.gradle | 19 ++++++++++++++----- 3 files changed, 14 insertions(+), 22 deletions(-) diff --git a/example/android/app/build.gradle b/example/android/app/build.gradle index 2ff64859c..85e5e3b25 100644 --- a/example/android/app/build.gradle +++ b/example/android/app/build.gradle @@ -62,7 +62,3 @@ android { flutter { source '../..' } - -dependencies { - implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" -} diff --git a/example/android/build.gradle b/example/android/build.gradle index 6554ae975..bc157bd1a 100644 --- a/example/android/build.gradle +++ b/example/android/build.gradle @@ -1,16 +1,3 @@ -buildscript { - ext.kotlin_version = '1.9.22' - repositories { - google() - mavenCentral() - } - - dependencies { - classpath 'com.android.tools.build:gradle:8.2.2' - classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" - } -} - allprojects { repositories { google() diff --git a/example/android/settings.gradle b/example/android/settings.gradle index 05dfe8308..26f682fa1 100644 --- a/example/android/settings.gradle +++ b/example/android/settings.gradle @@ -5,12 +5,21 @@ pluginManagement { def flutterSdkPath = properties.getProperty("flutter.sdk") assert flutterSdkPath != null, "flutter.sdk not set in local.properties" return flutterSdkPath - } - settings.ext.flutterSdkPath = flutterSdkPath() + }() + + includeBuild("$flutterSdkPath/packages/flutter_tools/gradle") - includeBuild("${settings.ext.flutterSdkPath}/packages/flutter_tools/gradle") + repositories { + google() + mavenCentral() + gradlePluginPortal() + } } -include ":app" +plugins { + id "dev.flutter.flutter-plugin-loader" version "1.0.0" + id "com.android.application" version "8.2.2" apply false + id "org.jetbrains.kotlin.android" version "1.9.22" apply false +} -apply from: "${settings.ext.flutterSdkPath}/packages/flutter_tools/gradle/app_plugin_loader.gradle" +include ":app" \ No newline at end of file From f31b03dcfb46ff4610f9c4941fe1d266a44cf200 Mon Sep 17 00:00:00 2001 From: Koji Wakamiya Date: Sat, 9 Mar 2024 11:35:24 +0900 Subject: [PATCH 23/26] feat: Update extension type --- lib/src/web/javascript_map.dart | 6 +- lib/src/web/zxing/result.dart | 39 +++++------- lib/src/web/zxing/result_point.dart | 17 +---- lib/src/web/zxing/zxing_barcode_reader.dart | 62 +++++++------------ .../zxing_browser_multi_format_reader.dart | 7 +-- 5 files changed, 44 insertions(+), 87 deletions(-) diff --git a/lib/src/web/javascript_map.dart b/lib/src/web/javascript_map.dart index e096ada89..5b4457641 100644 --- a/lib/src/web/javascript_map.dart +++ b/lib/src/web/javascript_map.dart @@ -9,12 +9,10 @@ import 'dart:js_interop'; /// /// Object literals can be made using [jsify]. @JS('Map') -@staticInterop -class JSMap { +extension type JSMap._(JSObject _) + implements JSObject { external factory JSMap(); -} -extension JSMapExtension on JSMap { external V? get(K key); external JSVoid set(K key, V? value); } diff --git a/lib/src/web/zxing/result.dart b/lib/src/web/zxing/result.dart index 67a60ee98..3705d1070 100644 --- a/lib/src/web/zxing/result.dart +++ b/lib/src/web/zxing/result.dart @@ -10,32 +10,27 @@ import 'package:mobile_scanner/src/web/zxing/result_point.dart'; /// The JS static interop class for the Result class in the ZXing library. /// /// See also: https://github.com/zxing-js/library/blob/master/src/core/Result.ts -@JS() -@anonymous -@staticInterop -abstract class Result {} - -extension ResultExt on Result { +extension type Result(JSObject _) implements JSObject { @JS('barcodeFormat') - external JSNumber? get _barcodeFormat; + external int? get _barcodeFormat; - @JS('text') - external JSString? get _text; + /// Get the text of the result. + external String? get text; @JS('rawBytes') external JSUint8Array? get _rawBytes; @JS('resultPoints') - external JSArray? get _resultPoints; + external JSArray? get _resultPoints; - @JS('timestamp') - external JSNumber? get _timestamp; + /// Get the timestamp of the result. + external int? get timestamp; /// Get the barcode format of the result. /// /// See also https://github.com/zxing-js/library/blob/master/src/core/BarcodeFormat.ts BarcodeFormat get barcodeFormat { - switch (_barcodeFormat?.toDartInt) { + switch (_barcodeFormat) { case 0: return BarcodeFormat.aztec; case 1: @@ -79,28 +74,22 @@ extension ResultExt on Result { } } + /// Get the raw bytes of the result. + Uint8List? get rawBytes => _rawBytes?.toDart; + /// Get the corner points of the result. List get resultPoints { - final JSArray? points = _resultPoints; + final JSArray? points = _resultPoints; if (points == null) { - return []; + return const []; } - return points.toDart.cast().map((point) { + return points.toDart.map((point) { return Offset(point.x, point.y); }).toList(); } - /// Get the raw bytes of the result. - Uint8List? get rawBytes => _rawBytes?.toDart; - - /// Get the text of the result. - String? get text => _text?.toDart; - - /// Get the timestamp of the result. - int? get timestamp => _timestamp?.toDartInt; - /// Convert this result to a [Barcode]. Barcode get toBarcode { return Barcode( diff --git a/lib/src/web/zxing/result_point.dart b/lib/src/web/zxing/result_point.dart index fa143e5b0..7468b26e5 100644 --- a/lib/src/web/zxing/result_point.dart +++ b/lib/src/web/zxing/result_point.dart @@ -3,21 +3,10 @@ import 'dart:js_interop'; /// The JS static interop class for the Result class in the ZXing library. /// /// See also: https://github.com/zxing-js/library/blob/master/src/core/ResultPoint.ts -@JS() -@anonymous -@staticInterop -abstract class ResultPoint {} - -extension ResultPointExt on ResultPoint { - @JS('x') - external JSNumber get _x; - - @JS('y') - external JSNumber get _y; - +extension type ResultPoint(JSObject _) implements JSObject { /// The x coordinate of the point. - double get x => _x.toDartDouble; + external double get x; /// The y coordinate of the point. - double get y => _y.toDartDouble; + external double get y; } diff --git a/lib/src/web/zxing/zxing_barcode_reader.dart b/lib/src/web/zxing/zxing_barcode_reader.dart index 5a4fbb140..c2be7be36 100644 --- a/lib/src/web/zxing/zxing_barcode_reader.dart +++ b/lib/src/web/zxing/zxing_barcode_reader.dart @@ -48,42 +48,6 @@ final class ZXingBarcodeReader extends BarcodeReader { @override String get scriptUrl => 'https://unpkg.com/@zxing/library@0.19.1'; - /// Get the barcode format from the ZXing library, for the given [format]. - static int getZXingBarcodeFormat(BarcodeFormat format) { - switch (format) { - 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; - } - } - JSMap? _createReaderHints(List formats) { if (formats.isEmpty || formats.contains(BarcodeFormat.all)) { return null; @@ -96,8 +60,7 @@ final class ZXingBarcodeReader extends BarcodeReader { hints.set( 2.toJS, [ - for (final BarcodeFormat format in formats) - getZXingBarcodeFormat(format).toJS, + for (final BarcodeFormat format in formats) format.toJS, ].toJS, ); @@ -185,7 +148,7 @@ final class ZXingBarcodeReader extends BarcodeReader { _reader = ZXingBrowserMultiFormatReader( _createReaderHints(formats), - detectionTimeoutMs.toJS, + detectionTimeoutMs, ); await _prepareVideoElement(videoElement, videoStream); @@ -199,3 +162,24 @@ final class ZXingBarcodeReader extends BarcodeReader { _reader = null; } } + +extension on BarcodeFormat { + /// Get the barcode format from the ZXing library. + JSNumber get toJS => switch (this) { + BarcodeFormat.aztec => 0, + BarcodeFormat.codabar => 1, + BarcodeFormat.code39 => 2, + BarcodeFormat.code93 => 3, + BarcodeFormat.code128 => 4, + BarcodeFormat.dataMatrix => 5, + BarcodeFormat.ean8 => 6, + BarcodeFormat.ean13 => 7, + BarcodeFormat.itf => 8, + BarcodeFormat.pdf417 => 10, + BarcodeFormat.qrCode => 11, + BarcodeFormat.upcA => 14, + BarcodeFormat.upcE => 15, + BarcodeFormat.unknown || BarcodeFormat.all || _ => -1, + } + .toJS; +} diff --git a/lib/src/web/zxing/zxing_browser_multi_format_reader.dart b/lib/src/web/zxing/zxing_browser_multi_format_reader.dart index 0c1d63193..292cb6bb8 100644 --- a/lib/src/web/zxing/zxing_browser_multi_format_reader.dart +++ b/lib/src/web/zxing/zxing_browser_multi_format_reader.dart @@ -7,8 +7,7 @@ import 'package:web/web.dart'; /// /// See https://github.com/zxing-js/library/blob/master/src/browser/BrowserMultiFormatReader.ts @JS('ZXing.BrowserMultiFormatReader') -@staticInterop -class ZXingBrowserMultiFormatReader { +extension type ZXingBrowserMultiFormatReader._(JSObject _) implements JSObject { /// Construct a new `ZXing.BrowserMultiFormatReader`. /// /// The [hints] are the configuration options for the reader. @@ -17,11 +16,9 @@ class ZXingBrowserMultiFormatReader { /// See also: https://github.com/zxing-js/library/blob/master/src/core/DecodeHintType.ts external factory ZXingBrowserMultiFormatReader( JSMap? hints, - JSNumber? timeBetweenScansMillis, + int timeBetweenScansMillis, ); -} -extension ZXingBrowserMultiFormatReaderExt on ZXingBrowserMultiFormatReader { /// Attach a [MediaStream] to a [HTMLVideoElement]. /// /// This function accepts a [MediaStream] and a [HTMLVideoElement] as arguments, From dc119f73aad4540dd49f42010d1a70fe1948ef41 Mon Sep 17 00:00:00 2001 From: Navaron Bracke Date: Tue, 16 Apr 2024 15:46:55 +0200 Subject: [PATCH 24/26] fix missing JS annotations; use a switch expression; format --- lib/src/web/zxing/result.dart | 67 ++++++++------------- lib/src/web/zxing/result_point.dart | 1 + lib/src/web/zxing/zxing_barcode_reader.dart | 37 ++++++------ 3 files changed, 46 insertions(+), 59 deletions(-) diff --git a/lib/src/web/zxing/result.dart b/lib/src/web/zxing/result.dart index 3705d1070..568558384 100644 --- a/lib/src/web/zxing/result.dart +++ b/lib/src/web/zxing/result.dart @@ -10,6 +10,7 @@ import 'package:mobile_scanner/src/web/zxing/result_point.dart'; /// The JS static interop class for the Result class in the ZXing library. /// /// See also: https://github.com/zxing-js/library/blob/master/src/core/Result.ts +@JS() extension type Result(JSObject _) implements JSObject { @JS('barcodeFormat') external int? get _barcodeFormat; @@ -30,48 +31,30 @@ extension type Result(JSObject _) implements JSObject { /// /// See also https://github.com/zxing-js/library/blob/master/src/core/BarcodeFormat.ts BarcodeFormat get barcodeFormat { - switch (_barcodeFormat) { - case 0: - return BarcodeFormat.aztec; - case 1: - return BarcodeFormat.codabar; - 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: - // Maxicode - return BarcodeFormat.unknown; - case 10: - return BarcodeFormat.pdf417; - case 11: - return BarcodeFormat.qrCode; - case 12: - // RSS 14 - return BarcodeFormat.unknown; - case 13: - // RSS EXPANDED - return BarcodeFormat.unknown; - case 14: - return BarcodeFormat.upcA; - case 15: - return BarcodeFormat.upcE; - case 16: - // UPC/EAN extension - return BarcodeFormat.unknown; - default: - return BarcodeFormat.unknown; - } + return switch (_barcodeFormat) { + 0 => BarcodeFormat.aztec, + 1 => BarcodeFormat.codabar, + 2 => BarcodeFormat.code39, + 3 => BarcodeFormat.code93, + 4 => BarcodeFormat.code128, + 5 => BarcodeFormat.dataMatrix, + 6 => BarcodeFormat.ean8, + 7 => BarcodeFormat.ean13, + 8 => BarcodeFormat.itf, + // Maxicode + 9 => BarcodeFormat.unknown, + 10 => BarcodeFormat.pdf417, + 11 => BarcodeFormat.qrCode, + // RSS 14 + 12 => BarcodeFormat.unknown, + // RSS EXPANDED + 13 => BarcodeFormat.unknown, + 14 => BarcodeFormat.upcA, + 15 => BarcodeFormat.upcE, + // UPC/EAN extension + 16 => BarcodeFormat.unknown, + _ => BarcodeFormat.unknown + }; } /// Get the raw bytes of the result. diff --git a/lib/src/web/zxing/result_point.dart b/lib/src/web/zxing/result_point.dart index 7468b26e5..259c3ddee 100644 --- a/lib/src/web/zxing/result_point.dart +++ b/lib/src/web/zxing/result_point.dart @@ -3,6 +3,7 @@ import 'dart:js_interop'; /// The JS static interop class for the Result class in the ZXing library. /// /// See also: https://github.com/zxing-js/library/blob/master/src/core/ResultPoint.ts +@JS() extension type ResultPoint(JSObject _) implements JSObject { /// The x coordinate of the point. external double get x; diff --git a/lib/src/web/zxing/zxing_barcode_reader.dart b/lib/src/web/zxing/zxing_barcode_reader.dart index c2be7be36..bace6408b 100644 --- a/lib/src/web/zxing/zxing_barcode_reader.dart +++ b/lib/src/web/zxing/zxing_barcode_reader.dart @@ -165,21 +165,24 @@ final class ZXingBarcodeReader extends BarcodeReader { extension on BarcodeFormat { /// Get the barcode format from the ZXing library. - JSNumber get toJS => switch (this) { - BarcodeFormat.aztec => 0, - BarcodeFormat.codabar => 1, - BarcodeFormat.code39 => 2, - BarcodeFormat.code93 => 3, - BarcodeFormat.code128 => 4, - BarcodeFormat.dataMatrix => 5, - BarcodeFormat.ean8 => 6, - BarcodeFormat.ean13 => 7, - BarcodeFormat.itf => 8, - BarcodeFormat.pdf417 => 10, - BarcodeFormat.qrCode => 11, - BarcodeFormat.upcA => 14, - BarcodeFormat.upcE => 15, - BarcodeFormat.unknown || BarcodeFormat.all || _ => -1, - } - .toJS; + JSNumber get toJS { + final int zxingFormat = switch (this) { + BarcodeFormat.aztec => 0, + BarcodeFormat.codabar => 1, + BarcodeFormat.code39 => 2, + BarcodeFormat.code93 => 3, + BarcodeFormat.code128 => 4, + BarcodeFormat.dataMatrix => 5, + BarcodeFormat.ean8 => 6, + BarcodeFormat.ean13 => 7, + BarcodeFormat.itf => 8, + BarcodeFormat.pdf417 => 10, + BarcodeFormat.qrCode => 11, + BarcodeFormat.upcA => 14, + BarcodeFormat.upcE => 15, + BarcodeFormat.unknown || BarcodeFormat.all || _ => -1, + }; + + return zxingFormat.toJS; + } } From 220fc341905ea70bd1816d7e9bc584c436d231b6 Mon Sep 17 00:00:00 2001 From: Navaron Bracke Date: Tue, 16 Apr 2024 15:53:21 +0200 Subject: [PATCH 25/26] remove redundant cast --- lib/src/web/zxing/zxing_barcode_reader.dart | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/lib/src/web/zxing/zxing_barcode_reader.dart b/lib/src/web/zxing/zxing_barcode_reader.dart index bace6408b..bf828d039 100644 --- a/lib/src/web/zxing/zxing_barcode_reader.dart +++ b/lib/src/web/zxing/zxing_barcode_reader.dart @@ -12,8 +12,6 @@ import 'package:mobile_scanner/src/web/zxing/result.dart'; import 'package:mobile_scanner/src/web/zxing/zxing_browser_multi_format_reader.dart'; import 'package:web/web.dart' as web; -// TODO: remove the JSAny casts once upgraded to a package:web version that restores "implements JSAny" - /// A barcode reader implementation that uses the ZXing library. final class ZXingBarcodeReader extends BarcodeReader { ZXingBarcodeReader(); @@ -77,7 +75,7 @@ final class ZXingBarcodeReader extends BarcodeReader { web.MediaStream videoStream, ) async { final JSPromise? result = _reader?.attachStreamToVideo.callAsFunction( - _reader as JSAny?, + _reader, videoStream, videoElement, ) as JSPromise?; @@ -98,7 +96,7 @@ final class ZXingBarcodeReader extends BarcodeReader { controller.onListen = () { _reader?.decodeContinuously.callAsFunction( - _reader as JSAny?, + _reader, _reader?.videoElement, (Result? result, JSAny? error) { if (controller.isClosed || result == null) { @@ -118,8 +116,8 @@ final class ZXingBarcodeReader extends BarcodeReader { // when the stream subscription returned by this method is cancelled in `MobileScannerWeb.stop()`. // This avoids both leaving the barcode scanner running and a memory leak for the stream subscription. controller.onCancel = () async { - _reader?.stopContinuousDecode.callAsFunction(_reader as JSAny?); - _reader?.reset.callAsFunction(_reader as JSAny?); + _reader?.stopContinuousDecode.callAsFunction(_reader); + _reader?.reset.callAsFunction(_reader); await controller.close(); }; @@ -157,8 +155,8 @@ final class ZXingBarcodeReader extends BarcodeReader { @override Future stop() async { _onMediaTrackSettingsChanged = null; - _reader?.stopContinuousDecode.callAsFunction(_reader as JSAny?); - _reader?.reset.callAsFunction(_reader as JSAny?); + _reader?.stopContinuousDecode.callAsFunction(_reader); + _reader?.reset.callAsFunction(_reader); _reader = null; } } From 5c1e8ce0b14d22cba0be2f65fcdddc5ad9d0bae2 Mon Sep 17 00:00:00 2001 From: Navaron Bracke Date: Tue, 16 Apr 2024 16:11:23 +0200 Subject: [PATCH 26/26] bump version to 5.0.0-beta.3 --- CHANGELOG.md | 3 +++ pubspec.yaml | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7eae37e5c..2622377a5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,9 @@ Bugs fixed: * Fixed an issue where the camera preview and barcode scanner did not work the second time on web. +Improvements: +* [web] Migrates to extension types. (thanks @koji-1009 !) + ## 5.0.0-beta.2 Bugs fixed: diff --git a/pubspec.yaml b/pubspec.yaml index 449055b44..33b425138 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ 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: 5.0.0-beta.2 +version: 5.0.0-beta.3 repository: https://github.com/juliansteenbakker/mobile_scanner screenshots: