diff --git a/.flutter-plugins b/.flutter-plugins index 975d4fe..2630775 100644 --- a/.flutter-plugins +++ b/.flutter-plugins @@ -1,2 +1,13 @@ # This is a generated file; do not edit or check into version control. -mobile_scanner=/Users/arvindsangwan/.pub-cache/hosted/pub.dev/mobile_scanner-3.5.5/ +file_selector_linux=/Users/arvindsangwan/.pub-cache/hosted/pub.dev/file_selector_linux-0.9.2+1/ +file_selector_macos=/Users/arvindsangwan/.pub-cache/hosted/pub.dev/file_selector_macos-0.9.4/ +file_selector_windows=/Users/arvindsangwan/.pub-cache/hosted/pub.dev/file_selector_windows-0.9.3+1/ +flutter_plugin_android_lifecycle=/Users/arvindsangwan/.pub-cache/hosted/pub.dev/flutter_plugin_android_lifecycle-2.0.19/ +image_picker=/Users/arvindsangwan/.pub-cache/hosted/pub.dev/image_picker-1.1.1/ +image_picker_android=/Users/arvindsangwan/.pub-cache/hosted/pub.dev/image_picker_android-0.8.12+1/ +image_picker_for_web=/Users/arvindsangwan/.pub-cache/hosted/pub.dev/image_picker_for_web-3.0.4/ +image_picker_ios=/Users/arvindsangwan/.pub-cache/hosted/pub.dev/image_picker_ios-0.8.11+2/ +image_picker_linux=/Users/arvindsangwan/.pub-cache/hosted/pub.dev/image_picker_linux-0.2.1+1/ +image_picker_macos=/Users/arvindsangwan/.pub-cache/hosted/pub.dev/image_picker_macos-0.2.1+1/ +image_picker_windows=/Users/arvindsangwan/.pub-cache/hosted/pub.dev/image_picker_windows-0.2.1+1/ +mobile_scanner=/Users/arvindsangwan/.pub-cache/hosted/pub.dev/mobile_scanner-5.1.1/ diff --git a/.flutter-plugins-dependencies b/.flutter-plugins-dependencies index 3e2bbe1..9057c2b 100644 --- a/.flutter-plugins-dependencies +++ b/.flutter-plugins-dependencies @@ -1 +1 @@ -{"info":"This is a generated file; do not edit or check into version control.","plugins":{"ios":[{"name":"mobile_scanner","path":"/Users/arvindsangwan/.pub-cache/hosted/pub.dev/mobile_scanner-3.5.5/","native_build":true,"dependencies":[]}],"android":[{"name":"mobile_scanner","path":"/Users/arvindsangwan/.pub-cache/hosted/pub.dev/mobile_scanner-3.5.5/","native_build":true,"dependencies":[]}],"macos":[{"name":"mobile_scanner","path":"/Users/arvindsangwan/.pub-cache/hosted/pub.dev/mobile_scanner-3.5.5/","native_build":true,"dependencies":[]}],"linux":[],"windows":[],"web":[{"name":"mobile_scanner","path":"/Users/arvindsangwan/.pub-cache/hosted/pub.dev/mobile_scanner-3.5.5/","dependencies":[]}]},"dependencyGraph":[{"name":"mobile_scanner","dependencies":[]}],"date_created":"2024-01-08 21:11:33.102164","version":"3.16.4"} \ No newline at end of file +{"info":"This is a generated file; do not edit or check into version control.","plugins":{"ios":[{"name":"image_picker_ios","path":"/Users/arvindsangwan/.pub-cache/hosted/pub.dev/image_picker_ios-0.8.11+2/","native_build":true,"dependencies":[]},{"name":"mobile_scanner","path":"/Users/arvindsangwan/.pub-cache/hosted/pub.dev/mobile_scanner-5.1.1/","native_build":true,"dependencies":[]}],"android":[{"name":"flutter_plugin_android_lifecycle","path":"/Users/arvindsangwan/.pub-cache/hosted/pub.dev/flutter_plugin_android_lifecycle-2.0.19/","native_build":true,"dependencies":[]},{"name":"image_picker_android","path":"/Users/arvindsangwan/.pub-cache/hosted/pub.dev/image_picker_android-0.8.12+1/","native_build":true,"dependencies":["flutter_plugin_android_lifecycle"]},{"name":"mobile_scanner","path":"/Users/arvindsangwan/.pub-cache/hosted/pub.dev/mobile_scanner-5.1.1/","native_build":true,"dependencies":[]}],"macos":[{"name":"file_selector_macos","path":"/Users/arvindsangwan/.pub-cache/hosted/pub.dev/file_selector_macos-0.9.4/","native_build":true,"dependencies":[]},{"name":"image_picker_macos","path":"/Users/arvindsangwan/.pub-cache/hosted/pub.dev/image_picker_macos-0.2.1+1/","native_build":false,"dependencies":["file_selector_macos"]},{"name":"mobile_scanner","path":"/Users/arvindsangwan/.pub-cache/hosted/pub.dev/mobile_scanner-5.1.1/","native_build":true,"dependencies":[]}],"linux":[{"name":"file_selector_linux","path":"/Users/arvindsangwan/.pub-cache/hosted/pub.dev/file_selector_linux-0.9.2+1/","native_build":true,"dependencies":[]},{"name":"image_picker_linux","path":"/Users/arvindsangwan/.pub-cache/hosted/pub.dev/image_picker_linux-0.2.1+1/","native_build":false,"dependencies":["file_selector_linux"]}],"windows":[{"name":"file_selector_windows","path":"/Users/arvindsangwan/.pub-cache/hosted/pub.dev/file_selector_windows-0.9.3+1/","native_build":true,"dependencies":[]},{"name":"image_picker_windows","path":"/Users/arvindsangwan/.pub-cache/hosted/pub.dev/image_picker_windows-0.2.1+1/","native_build":false,"dependencies":["file_selector_windows"]}],"web":[{"name":"image_picker_for_web","path":"/Users/arvindsangwan/.pub-cache/hosted/pub.dev/image_picker_for_web-3.0.4/","dependencies":[]},{"name":"mobile_scanner","path":"/Users/arvindsangwan/.pub-cache/hosted/pub.dev/mobile_scanner-5.1.1/","dependencies":[]}]},"dependencyGraph":[{"name":"file_selector_linux","dependencies":[]},{"name":"file_selector_macos","dependencies":[]},{"name":"file_selector_windows","dependencies":[]},{"name":"flutter_plugin_android_lifecycle","dependencies":[]},{"name":"image_picker","dependencies":["image_picker_android","image_picker_for_web","image_picker_ios","image_picker_linux","image_picker_macos","image_picker_windows"]},{"name":"image_picker_android","dependencies":["flutter_plugin_android_lifecycle"]},{"name":"image_picker_for_web","dependencies":[]},{"name":"image_picker_ios","dependencies":[]},{"name":"image_picker_linux","dependencies":["file_selector_linux"]},{"name":"image_picker_macos","dependencies":["file_selector_macos"]},{"name":"image_picker_windows","dependencies":["file_selector_windows"]},{"name":"mobile_scanner","dependencies":[]}],"date_created":"2024-05-25 23:09:40.081612","version":"3.22.1"} \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 8f3fbf5..f51c6da 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +## 5.1.1 + +**BREAKING CHANGES:** + +- mobile_scanner: ^5.1.1 + ## 3.4.1 - Jump to 3.4.1 to match the version of mobile_scanner diff --git a/README.md b/README.md index 41f3a3f..834fd17 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ [![pub package](https://img.shields.io/pub/v/ai_barcode_scanner.svg)](https://pub.dev/packages/ai_barcode_scanner) [![GitHub Sponsors](https://img.shields.io/github/sponsors/juliansteenbakker?label=Sponsor%20Julian%20Steenbakker!)](https://github.com/sponsors/juliansteenbakker) -### Screenshots +### Screenshots @@ -16,13 +16,12 @@
- ## Features Supported See the example app for detailed implementation information. | Features | Android | iOS | macOS | Web | -|------------------------|--------------------|--------------------|-------|-----| +| ---------------------- | ------------------ | ------------------ | ----- | --- | | analyzeImage (Gallery) | :heavy_check_mark: | :heavy_check_mark: | :x: | :x: | | returnImage | :heavy_check_mark: | :heavy_check_mark: | :x: | :x: | | scanWindow | :heavy_check_mark: | :heavy_check_mark: | :x: | :x: | @@ -31,19 +30,27 @@ See the example app for detailed implementation information. ## Platform Support | Android | iOS | macOS | Web | Linux | Windows | -|---------|-----|-------|-----|-------|---------| +| ------- | --- | ----- | --- | ----- | ------- | | ✔ | ✔ | ✔ | ✔ | :x: | :x: | - - ## Platform specific setup + ### Android -This packages uses the **bundled version** of MLKit Barcode-scanning for Android. This version is more accurate and immediately available to devices. However, this version will increase the size of the app with approximately 3 to 10 MB. The alternative for this is to use the **unbundled version** of MLKit Barcode-scanning for Android. This version is older than the bundled version however this only increases the size by around 600KB. -To use this version you must alter the mobile_scanner gradle file to replace `com.google.mlkit:barcode-scanning:17.0.2` with `com.google.android.gms:play-services-mlkit-barcode-scanning:18.0.0`. Keep in mind that if you alter the gradle files directly in your project it can be overriden when you update your pubspec.yaml. I am still searching for a way to properly replace the module in gradle but have yet to find one. + +This package uses by default the **bundled version** of MLKit Barcode-scanning for Android. This version is immediately available to the device. But it will increase the size of the app by approximately 3 to 10 MB. + +The alternative is to use the **unbundled version** of MLKit Barcode-scanning for Android. This version is downloaded on first use via Google Play Services. It increases the app size by around 600KB. [You can read more about the difference between the two versions here.](https://developers.google.com/ml-kit/vision/barcode-scanning/android) +To use the **unbundled version** of the MLKit Barcode-scanning, add the following line to your `/android/gradle.properties` file: + +``` +dev.steenbakker.mobile_scanner.useUnbundled=true +``` + ### iOS + **Add the following keys to your Info.plist file, located in /ios/Runner/Info.plist:** NSCameraUsageDescription - describe why your app needs access to the camera. This is called Privacy - Camera Usage Description in the visual editor. @@ -51,24 +58,40 @@ NSCameraUsageDescription - describe why your app needs access to the camera. Thi NSPhotoLibraryUsageDescription - describe why your app needs permission for the photo library. This is called Privacy - Photo Library Usage Description in the visual editor. Example, - ``` - NSCameraUsageDescription - This app needs camera access to scan QR codes - - NSPhotoLibraryUsageDescription - This app needs photos access to get QR code from photo library - ``` +``` +NSCameraUsageDescription +This app needs camera access to scan QR codes + +NSPhotoLibraryUsageDescription +This app needs photos access to get QR code from photo library +``` ### macOS + Ensure that you granted camera permission in XCode -> Signing & Capabilities: Screenshot of XCode where Camera is checked ## Web -This package uses ZXing on web to read barcodes so it needs to be included in `index.html` as script. -```html - + +As of version 5.0.0 adding the library to the `index.html` is no longer required, +as the library is automatically loaded on first use. + +### Providing a mirror for the barcode scanning library + +If a different mirror is needed to load the barcode scanning library, +the source URL can be set beforehand. + +```dart +import 'package:flutter/foundation.dart'; +import 'package:mobile_scanner/mobile_scanner.dart'; + +final String scriptUrl = // ... + +if (kIsWeb) { + MobileScannerPlatform.instance.setBarcodeLibraryScriptUrl(scriptUrl); +} ``` ## Usage ([ai_barcode_scanner](https://pub.dev/packages/ai_barcode_scanner)) @@ -123,164 +146,89 @@ AiBarcodeScanner( ## Usage ([mobile_scanner](https://pub.dev/packages/mobile_scanner)) +Import the package with `package:mobile_scanner/mobile_scanner.dart`. -## Usage - -Import `package:mobile_scanner/mobile_scanner.dart`, and use the widget with or without the controller. - -If you don't provide a controller, you can't control functions like the torch(flash) or switching camera. - -If you don't set `detectionSpeed` to `DetectionSpeed.noDuplicates`, you can get multiple scans in a very short time, causing things like pop() to fire lots of times. - -Example without controller: +Create a new `MobileScannerController` controller, using the required options. +Provide a `StreamSubscription` for the barcode events. ```dart -import 'package:mobile_scanner/mobile_scanner.dart'; +final MobileScannerController controller = MobileScannerController( + // required options for the scanner +); - @override - Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar(title: const Text('Mobile Scanner')), - body: MobileScanner( - // fit: BoxFit.contain, - onDetect: (capture) { - final List barcodes = capture.barcodes; - final Uint8List? image = capture.image; - for (final barcode in barcodes) { - debugPrint('Barcode found! ${barcode.rawValue}'); - } - }, - ), - ); - } +StreamSubscription? _subscription; ``` -Example with controller and initial values: +Ensure that your `State` class mixes in `WidgetsBindingObserver`, to handle lifecyle changes: ```dart -import 'package:mobile_scanner/mobile_scanner.dart'; +class MyState extends State with WidgetsBindingObserver { + // ... @override - Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar(title: const Text('Mobile Scanner')), - body: MobileScanner( - // fit: BoxFit.contain, - controller: MobileScannerController( - detectionSpeed: DetectionSpeed.normal, - facing: CameraFacing.front, - torchEnabled: true, - ), - onDetect: (capture) { - final List barcodes = capture.barcodes; - final Uint8List? image = capture.image; - for (final barcode in barcodes) { - debugPrint('Barcode found! ${barcode.rawValue}'); - } - }, - ), - ); + void didChangeAppLifecycleState(AppLifecycleState state) { + // If the controller is not ready, do not try to start or stop it. + // Permission dialogs can trigger lifecycle changes before the controller is ready. + if (!controller.value.isInitialized) { + return; + } + + switch (state) { + case AppLifecycleState.detached: + case AppLifecycleState.hidden: + case AppLifecycleState.paused: + return; + case AppLifecycleState.resumed: + // Restart the scanner when the app is resumed. + // Don't forget to resume listening to the barcode events. + _subscription = controller.barcodes.listen(_handleBarcode); + + unawaited(controller.start()); + case AppLifecycleState.inactive: + // Stop the scanner when the app is paused. + // Also stop the barcode events subscription. + unawaited(_subscription?.cancel()); + _subscription = null; + unawaited(controller.stop()); + } } + + // ... +} ``` -Example with controller and torch & camera controls: +Then, start the scanner in `void initState()`: ```dart -import 'package:mobile_scanner/mobile_scanner.dart'; - - MobileScannerController cameraController = MobileScannerController(); - - @override - Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar( - title: const Text('Mobile Scanner'), - actions: [ - IconButton( - color: Colors.white, - icon: ValueListenableBuilder( - valueListenable: cameraController.torchState, - builder: (context, state, child) { - switch (state as TorchState) { - case TorchState.off: - return const Icon(Icons.flash_off, color: Colors.grey); - case TorchState.on: - return const Icon(Icons.flash_on, color: Colors.yellow); - } - }, - ), - iconSize: 32.0, - onPressed: () => cameraController.toggleTorch(), - ), - IconButton( - color: Colors.white, - icon: ValueListenableBuilder( - valueListenable: cameraController.cameraFacingState, - builder: (context, state, child) { - switch (state as CameraFacing) { - case CameraFacing.front: - return const Icon(Icons.camera_front); - case CameraFacing.back: - return const Icon(Icons.camera_rear); - } - }, - ), - iconSize: 32.0, - onPressed: () => cameraController.switchCamera(), - ), - ], - ), - body: MobileScanner( - // fit: BoxFit.contain, - controller: cameraController, - onDetect: (capture) { - final List barcodes = capture.barcodes; - final Uint8List? image = capture.image; - for (final barcode in barcodes) { - debugPrint('Barcode found! ${barcode.rawValue}'); - } - }, - ), - ); - } +@override +void initState() { + super.initState(); + // Start listening to lifecycle changes. + WidgetsBinding.instance.addObserver(this); + + // Start listening to the barcode events. + _subscription = controller.barcodes.listen(_handleBarcode); + + // Finally, start the scanner itself. + unawaited(controller.start()); +} ``` -Example with controller and returning images +Finally, dispose of the the `MobileScannerController` when you are done with it. ```dart -import 'package:mobile_scanner/mobile_scanner.dart'; - - @override - Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar(title: const Text('Mobile Scanner')), - body: MobileScanner( - fit: BoxFit.contain, - controller: MobileScannerController( - // facing: CameraFacing.back, - // torchEnabled: false, - returnImage: true, - ), - onDetect: (capture) { - final List barcodes = capture.barcodes; - final Uint8List? image = capture.image; - for (final barcode in barcodes) { - debugPrint('Barcode found! ${barcode.rawValue}'); - } - if (image != null) { - showDialog( - context: context, - builder: (context) => - Image(image: MemoryImage(image)), - ); - Future.delayed(const Duration(seconds: 5), () { - Navigator.pop(context); - }); - } - }, - ), - ); - } +@override +Future dispose() async { + // Stop listening to lifecycle changes. + WidgetsBinding.instance.removeObserver(this); + // Stop listening to the barcode events. + unawaited(_subscription?.cancel()); + _subscription = null; + // Dispose the widget itself. + super.dispose(); + // Finally, dispose of the controller. + await controller.dispose(); +} ``` ### BarcodeCapture @@ -288,14 +236,14 @@ import 'package:mobile_scanner/mobile_scanner.dart'; The onDetect function returns a BarcodeCapture objects which contains the following items. | Property name | Type | Description | -|---------------|---------------|-----------------------------------| +| ------------- | ------------- | --------------------------------- | | barcodes | List | A list with scanned barcodes. | | image | Uint8List? | If enabled, an image of the scan. | You can use the following properties of the Barcode object. | Property name | Type | Description | -|---------------|----------------|-------------------------------------| +| ------------- | -------------- | ----------------------------------- | | format | BarcodeFormat | | | rawBytes | Uint8List? | binary scan result | | rawValue | String? | Value if barcode is in UTF-8 format | @@ -313,121 +261,180 @@ You can use the following properties of the Barcode object. ### Constructor parameters for [ai_barcode_scanner](https://pub.dev/packages/ai_barcode_scanner) -```dart - /// Function that gets Called when barcode is scanned successfully -/// -final void Function(String) onScan; - -/// Function that gets called when a Barcode is detected. -/// -/// [barcode] The barcode object with all information about the scanned code. -/// [args] Information about the state of the MobileScanner widget -final void Function(BarcodeCapture)? onDetect; - -/// Validate barcode text with a function -final bool Function(String value)? validator; - -/// Fit to screen -final BoxFit fit; - -/// Barcode controller (optional) -final MobileScannerController? controller; - -/// Show overlay or not (default: true) -final bool showOverlay; - -/// Overlay border color (default: white) -final Color borderColor; - -/// Overlay border width (default: 10) -final double borderWidth; - -/// Overlay color -final Color overlayColor; - -/// Overlay border radius (default: 10) -final double borderRadius; - -/// Overlay border length (default: 30) -final double borderLength; - -/// Overlay cut out width (optional) -final double? cutOutWidth; - -/// Overlay cut out height (optional) -final double? cutOutHeight; - -/// Overlay cut out offset (default: 0) -final double cutOutBottomOffset; - -/// Overlay cut out size (default: 300) -final double cutOutSize; - -/// Hint widget (optional) (default: Text('Scan QR Code')) -/// Hint widget will be replaced the bottom of the screen. -/// If you want to replace the bottom screen widget, use [bottomBar] -final Widget? bottomBar; - -/// Hint text (default: 'Scan QR Code') -final String bottomBarText; - -/// Hint text style -final TextStyle bottomBarTextStyle; - -/// Show error or not (default: true) -final bool showError; - -/// Error color (default: red) -final Color errorColor; - -/// Show success or not (default: true) -final bool showSuccess; - -/// Success color (default: green) -final Color successColor; - -/// Can auto back to previous page when barcode is successfully scanned (default: true) -final bool canPop; - -/// The function that builds an error widget when the scanner -/// could not be started. -/// -/// If this is null, defaults to a black [ColoredBox] -/// with a centered white [Icons.error] icon. -final Widget Function(BuildContext, MobileScannerException, Widget?)? -errorBuilder; - -/// The function that builds a placeholder widget when the scanner -/// is not yet displaying its camera preview. -/// -/// If this is null, a black [ColoredBox] is used as placeholder. -final Widget Function(BuildContext, Widget?)? placeholderBuilder; - -/// The function that signals when the barcode scanner is started. -final void Function(MobileScannerArguments?)? onScannerStarted; - -/// Called when this object is removed from the tree permanently. -final void Function()? onDispose; - -/// if set barcodes will only be scanned if they fall within this [Rect] -/// useful for having a cut-out overlay for example. these [Rect] -/// coordinates are relative to the widget size, so by how much your -/// rectangle overlays the actual image can depend on things like the -/// [BoxFit] -final Rect? scanWindow; - -/// Only set this to true if you are starting another instance of mobile_scanner -/// right after disposing the first one, like in a PageView. -/// -/// Default: false -final bool? startDelay; - -/// Appbar widget -/// you can use this to add appbar to the scanner screen -/// -final PreferredSizeWidget? appBar; - -``` +````dart + /// Fit to screen + final BoxFit fit; + + /// Barcode controller (optional) + final MobileScannerController? controller; + + /// Show overlay or not (default: true) + final bool showOverlay; + + /// Overlay border color (default: white) + final Color? borderColor; + + /// Overlay border width (default: 10) + final double borderWidth; + + /// Overlay color + final Color overlayColor; + + /// Overlay border radius (default: 10) + final double borderRadius; + + /// Overlay border length (default: 30) + final double borderLength; + + /// Overlay cut out width (optional) + final double? cutOutWidth; + + /// Overlay cut out height (optional) + final double? cutOutHeight; + + /// Overlay cut out offset (default: 0) + final double cutOutBottomOffset; + + /// Overlay cut out size (default: 300) + final double cutOutSize; + + /// Show error or not (default: true) + final bool showError; + + /// Error color (default: red) + final Color errorColor; + + /// Show success or not (default: true) + final bool showSuccess; + + /// Success color (default: green) + final Color successColor; + + /// The function that builds an error widget when the scanner + /// could not be started. + /// + /// If this is null, defaults to a black [ColoredBox] + /// with a centered white [Icons.error] icon. + final Widget Function(BuildContext, MobileScannerException, Widget?)? + errorBuilder; + + /// The function that builds a placeholder widget when the scanner + /// is not yet displaying its camera preview. + /// + /// If this is null, a black [ColoredBox] is used as placeholder. + final Widget Function(BuildContext, Widget?)? placeholderBuilder; + + /// Called when this object is removed from the tree permanently. + final void Function()? onDispose; + + /// AppBar widget + /// you can use this to add appBar to the scanner screen + /// + final PreferredSizeWidget? appBar; + + /// The builder for the overlay above the camera preview. + /// + /// The resulting widget can be combined with the [scanWindow] rectangle + /// to create a cutout for the camera preview. + /// + /// The [BoxConstraints] for this builder + /// are the same constraints that are used to compute the effective [scanWindow]. + /// + /// The overlay is only displayed when the camera preview is visible. + final LayoutWidgetBuilder? overlayBuilder; + + /// The scan window rectangle for the barcode scanner. + /// + /// If this is not null, the barcode scanner will only scan barcodes + /// which intersect this rectangle. + /// + /// This rectangle is relative to the layout size + /// of the *camera preview widget* in the widget tree, + /// rather than the actual size of the camera preview output. + /// This is because the size of the camera preview widget + /// might not be the same as the size of the camera output. + /// + /// For example, the applied [fit] has an effect on the size of the camera preview widget, + /// while the camera preview size remains the same. + /// + /// The following example shows a scan window that is centered, + /// fills half the height and one third of the width of the layout: + /// + /// ```dart + /// LayoutBuider( + /// builder: (BuildContext context, BoxConstraints constraints) { + /// final Size layoutSize = constraints.biggest; + /// + /// final double scanWindowWidth = layoutSize.width / 3; + /// final double scanWindowHeight = layoutSize.height / 2; + /// + /// final Rect scanWindow = Rect.fromCenter( + /// center: layoutSize.center(Offset.zero), + /// width: scanWindowWidth, + /// height: scanWindowHeight, + /// ); + /// } + /// ); + /// ``` + final Rect? scanWindow; + + /// The threshold for updates to the [scanWindow]. + /// + /// If the [scanWindow] would be updated, + /// due to new layout constraints for the scanner, + /// and the width or height of the new scan window have not changed by this threshold, + /// then the scan window is not updated. + /// + /// It is recommended to set this threshold + /// if scan window updates cause performance issues. + /// + /// Defaults to no threshold for scan window updates. + /// + final void Function(BarcodeCapture)? onDetect; + + /// The threshold for updates to the [scanWindow]. + /// + /// If the [scanWindow] would be updated, + /// due to new layout constraints for the scanner, + /// and the width or height of the new scan window have not changed by this threshold, + /// then the scan window is not updated. + /// + /// It is recommended to set this threshold + /// if scan window updates cause performance issues. + /// + /// Defaults to no threshold for scan window updates. + final double scanWindowUpdateThreshold; + + /// Validator function to check if barcode is valid or not + final bool Function(BarcodeCapture)? validator; + + final void Function(String?)? onImagePick; + + /// Title for the draggable sheet (default: 'Scan any QR code') + final String title; + + /// Child widget for the draggable sheet (default: SizedBox.shrink()) + final Widget child; + + /// Hide drag handler of the draggable sheet (default: false) + final bool hideDragHandler; + + /// Hide title of the draggable sheet (default: false) + final bool hideTitle; + + /// Upload from gallery button alignment + /// default: bottom center, center, 0.75 + final AlignmentGeometry? buttonAlignment; + + /// actions for the app bar (optional) + /// Camera switch and torch toggle buttons are added by default + /// You can add more actions to the app bar using this parameter + final List? actions; + + /// Optional function to be called when clicking the back button on the app bar + /// If not provided, the default behavior is to pop the current route from the navigator + final void Function()? onPop; +```` ### Contributing to [ai_barcode_scanner](https://pub.dev/packages/ai_barcode_scanner) diff --git a/example/.gitignore b/example/.gitignore index a8e938c..1eab1a2 100644 --- a/example/.gitignore +++ b/example/.gitignore @@ -33,7 +33,6 @@ migrate_working_dir/ /build/ # Web related -lib/generated_plugin_registrant.dart # Symbolication related app.*.symbols diff --git a/example/android/app/build.gradle b/example/android/app/build.gradle index b3946ff..bac9b30 100644 --- a/example/android/app/build.gradle +++ b/example/android/app/build.gradle @@ -26,7 +26,7 @@ apply plugin: 'kotlin-android' apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" android { - compileSdkVersion 33 + compileSdkVersion 34 ndkVersion flutter.ndkVersion compileOptions { @@ -47,8 +47,8 @@ android { applicationId "com.mohesu.barcodescanner" // You can update the following values to match your application needs. // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-build-configuration. - minSdkVersion 21 - targetSdkVersion 31 + minSdkVersion 24 + targetSdkVersion 34 versionCode flutterVersionCode.toInteger() versionName flutterVersionName } diff --git a/example/ios/Flutter/AppFrameworkInfo.plist b/example/ios/Flutter/AppFrameworkInfo.plist index 9625e10..7c56964 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 16dc63d..4581fa1 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, '12.0' + platform :ios, '14.0' # CocoaPods analytics sends network stats synchronously affecting flutter build latency. ENV['COCOAPODS_DISABLE_STATS'] = 'true' diff --git a/example/ios/Runner.xcodeproj/project.pbxproj b/example/ios/Runner.xcodeproj/project.pbxproj index d9d19ac..c21c4fd 100644 --- a/example/ios/Runner.xcodeproj/project.pbxproj +++ b/example/ios/Runner.xcodeproj/project.pbxproj @@ -155,7 +155,7 @@ 97C146E61CF9000F007C117D /* Project object */ = { isa = PBXProject; attributes = { - LastUpgradeCheck = 1300; + LastUpgradeCheck = 1510; ORGANIZATIONNAME = ""; TargetAttributes = { 97C146ED1CF9000F007C117D = { @@ -204,6 +204,7 @@ files = ( ); inputPaths = ( + "${TARGET_BUILD_DIR}/${INFOPLIST_PATH}", ); name = "Thin Binary"; outputPaths = ( @@ -341,7 +342,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; @@ -420,7 +421,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; @@ -469,7 +470,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/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index c87d15a..5e31d3d 100644 --- a/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -1,6 +1,6 @@ createState() => _MyHomePageState(); -} - -class _MyHomePageState extends State { - String barcode = 'Tap to scan'; - @override - Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar( - title: const Text(' Scanner'), - ), - body: Center( - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - ElevatedButton( - child: const Text('Scan Barcode'), - onPressed: () async { - await Navigator.of(context).push( - MaterialPageRoute( - builder: (context) => AiBarcodeScanner( - validator: (value) { - return value.startsWith('https://'); - }, - canPop: false, - onScan: (String value) { - debugPrint(value); - setState(() { - barcode = value; - }); - }, - onDetect: (p0) {}, - onDispose: () { - debugPrint("Barcode scanner disposed!"); - }, - controller: MobileScannerController( - detectionSpeed: DetectionSpeed.noDuplicates, - ), - ), - ), - ); - }, - ), - Text(barcode), - ], - ), - ), + home: OverlayPage(), ); } } diff --git a/example/lib/pages/overlay.dart b/example/lib/pages/overlay.dart new file mode 100644 index 0000000..d518038 --- /dev/null +++ b/example/lib/pages/overlay.dart @@ -0,0 +1,55 @@ +import 'dart:developer'; + +import 'package:ai_barcode_scanner/ai_barcode_scanner.dart'; +import 'package:flutter/material.dart'; + +class OverlayPage extends StatefulWidget { + const OverlayPage({super.key}); + + @override + State createState() => _OverlayPageState(); +} + +class _OverlayPageState extends State { + String barcode = 'Tap to scan'; + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: const Text(' Scanner'), + ), + body: Center( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + ElevatedButton( + child: const Text('Scan Barcode'), + onPressed: () async { + await Navigator.of(context).push( + MaterialPageRoute( + builder: (context) => AiBarcodeScanner( + onDispose: () { + debugPrint("Barcode scanner disposed!"); + }, + controller: MobileScannerController( + detectionSpeed: DetectionSpeed.noDuplicates, + ), + onDetect: (p0) => setState(() { + barcode = p0.barcodes.first.rawValue.toString(); + log(barcode, name: 'Barcode'); + }), + validator: (p0) => + p0.barcodes.first.rawValue?.startsWith('https://') ?? + false, + ), + ), + ); + }, + ), + Text(barcode), + ], + ), + ), + ); + } +} diff --git a/example/linux/flutter/generated_plugin_registrant.cc b/example/linux/flutter/generated_plugin_registrant.cc index e71a16d..64a0ece 100644 --- a/example/linux/flutter/generated_plugin_registrant.cc +++ b/example/linux/flutter/generated_plugin_registrant.cc @@ -6,6 +6,10 @@ #include "generated_plugin_registrant.h" +#include void fl_register_plugins(FlPluginRegistry* registry) { + g_autoptr(FlPluginRegistrar) file_selector_linux_registrar = + fl_plugin_registry_get_registrar_for_plugin(registry, "FileSelectorPlugin"); + file_selector_plugin_register_with_registrar(file_selector_linux_registrar); } diff --git a/example/linux/flutter/generated_plugins.cmake b/example/linux/flutter/generated_plugins.cmake index 2e1de87..2db3c22 100644 --- a/example/linux/flutter/generated_plugins.cmake +++ b/example/linux/flutter/generated_plugins.cmake @@ -3,6 +3,7 @@ # list(APPEND FLUTTER_PLUGIN_LIST + file_selector_linux ) list(APPEND FLUTTER_FFI_PLUGIN_LIST diff --git a/example/macos/Flutter/GeneratedPluginRegistrant.swift b/example/macos/Flutter/GeneratedPluginRegistrant.swift index 553af1a..a220c2b 100644 --- a/example/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/example/macos/Flutter/GeneratedPluginRegistrant.swift @@ -5,12 +5,10 @@ import FlutterMacOS import Foundation -import firebase_auth -import firebase_core +import file_selector_macos import mobile_scanner func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { - FLTFirebaseAuthPlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseAuthPlugin")) - FLTFirebaseCorePlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseCorePlugin")) + FileSelectorPlugin.register(with: registry.registrar(forPlugin: "FileSelectorPlugin")) MobileScannerPlugin.register(with: registry.registrar(forPlugin: "MobileScannerPlugin")) } diff --git a/example/pubspec.lock b/example/pubspec.lock index f60fcbf..bcd5ae1 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -1,21 +1,13 @@ # Generated by pub # See https://dart.dev/tools/pub/glossary#lockfile packages: - _flutterfire_internals: - dependency: transitive - description: - name: _flutterfire_internals - sha256: "2d8e8e123ca3675625917f535fcc0d3a50092eef44334168f9b18adc050d4c6e" - url: "https://pub.dev" - source: hosted - version: "1.3.6" ai_barcode_scanner: dependency: "direct main" description: path: ".." relative: true source: path - version: "3.4.1" + version: "5.1.1" async: dependency: transitive description: @@ -56,14 +48,22 @@ packages: url: "https://pub.dev" source: hosted version: "1.18.0" + cross_file: + dependency: transitive + description: + name: cross_file + sha256: "55d7b444feb71301ef6b8838dbc1ae02e63dd48c8773f3810ff53bb1e2945b32" + url: "https://pub.dev" + source: hosted + version: "0.3.4+1" cupertino_icons: dependency: "direct main" description: name: cupertino_icons - sha256: d57953e10f9f8327ce64a508a355f0b1ec902193f66288e8cb5070e7c47eeb2d + sha256: ba631d1c7f7bef6b729a622b7b752645a2d076dba9976925b8f25725a30e1ee6 url: "https://pub.dev" source: hosted - version: "1.0.6" + version: "1.0.8" fake_async: dependency: transitive description: @@ -72,54 +72,38 @@ packages: url: "https://pub.dev" source: hosted version: "1.3.1" - firebase_auth: - dependency: "direct main" - description: - name: firebase_auth - sha256: e7b7e2a08888cb8ef073a9b6d4df9039ea0ff07c920d4258fb4886c55fe2dbb6 - url: "https://pub.dev" - source: hosted - version: "4.10.0" - firebase_auth_platform_interface: + file_selector_linux: dependency: transitive description: - name: firebase_auth_platform_interface - sha256: "4862bd649268e32ebd685a58abe1eb7ef13aef3f668ce9c56a7d283c971ec6ff" + name: file_selector_linux + sha256: "045d372bf19b02aeb69cacf8b4009555fb5f6f0b7ad8016e5f46dd1387ddd492" url: "https://pub.dev" source: hosted - version: "6.19.0" - firebase_auth_web: + version: "0.9.2+1" + file_selector_macos: dependency: transitive description: - name: firebase_auth_web - sha256: "090d531cc0dcf1e39edc0d64cffa18ccc5745b1fd1081a13ee1bf42d861ff764" + name: file_selector_macos + sha256: f42eacb83b318e183b1ae24eead1373ab1334084404c8c16e0354f9a3e55d385 url: "https://pub.dev" source: hosted - version: "5.8.1" - firebase_core: + version: "0.9.4" + file_selector_platform_interface: dependency: transitive description: - name: firebase_core - sha256: "675c209c94a1817649137cbd113fc4c9ae85e48d03dd578629abbec6d8a4d93d" + name: file_selector_platform_interface + sha256: a3994c26f10378a039faa11de174d7b78eb8f79e4dd0af2a451410c1a5c3f66b url: "https://pub.dev" source: hosted - version: "2.16.0" - firebase_core_platform_interface: + version: "2.6.2" + file_selector_windows: dependency: transitive description: - name: firebase_core_platform_interface - sha256: b63e3be6c96ef5c33bdec1aab23c91eb00696f6452f0519401d640938c94cba2 + name: file_selector_windows + sha256: d3547240c20cabf205c7c7f01a50ecdbc413755814d6677f3cb366f04abcead0 url: "https://pub.dev" source: hosted - version: "4.8.0" - firebase_core_web: - dependency: transitive - description: - name: firebase_core_web - sha256: e8c408923cd3a25bd342c576a114f2126769cd1a57106a4edeaa67ea4a84e962 - url: "https://pub.dev" - source: hosted - version: "2.8.0" + version: "0.9.3+1" flutter: dependency: "direct main" description: flutter @@ -129,10 +113,18 @@ packages: dependency: "direct dev" description: name: flutter_lints - sha256: a25a15ebbdfc33ab1cd26c63a6ee519df92338a9c10f122adda92938253bef04 + sha256: "3f41d009ba7172d5ff9be5f6e6e6abb4300e263aab8866d2a0842ed2a70f8f0c" + url: "https://pub.dev" + source: hosted + version: "4.0.0" + flutter_plugin_android_lifecycle: + dependency: transitive + description: + name: flutter_plugin_android_lifecycle + sha256: "8cf40eebf5dec866a6d1956ad7b4f7016e6c0cc69847ab946833b7d43743809f" url: "https://pub.dev" source: hosted - version: "2.0.3" + version: "2.0.19" flutter_test: dependency: "direct dev" description: flutter @@ -143,6 +135,14 @@ packages: description: flutter source: sdk version: "0.0.0" + http: + dependency: transitive + description: + name: http + sha256: "761a297c042deedc1ffbb156d6e2af13886bb305c2a343a4d972504cd67dd938" + url: "https://pub.dev" + source: hosted + version: "1.2.1" http_parser: dependency: transitive description: @@ -151,70 +151,158 @@ packages: url: "https://pub.dev" source: hosted version: "4.0.2" - js: + image_picker: + dependency: transitive + description: + name: image_picker + sha256: "33974eca2e87e8b4e3727f1b94fa3abcb25afe80b6bc2c4d449a0e150aedf720" + url: "https://pub.dev" + source: hosted + version: "1.1.1" + image_picker_android: + dependency: transitive + description: + name: image_picker_android + sha256: "0f57fee1e8bfadf8cc41818bbcd7f72e53bb768a54d9496355d5e8a5681a19f1" + url: "https://pub.dev" + source: hosted + version: "0.8.12+1" + image_picker_for_web: + dependency: transitive + description: + name: image_picker_for_web + sha256: "5d6eb13048cd47b60dbf1a5495424dea226c5faf3950e20bf8120a58efb5b5f3" + url: "https://pub.dev" + source: hosted + version: "3.0.4" + image_picker_ios: dependency: transitive description: - name: js - sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3 + name: image_picker_ios + sha256: "4824d8c7f6f89121ef0122ff79bb00b009607faecc8545b86bca9ab5ce1e95bf" url: "https://pub.dev" source: hosted - version: "0.6.7" + version: "0.8.11+2" + image_picker_linux: + dependency: transitive + description: + name: image_picker_linux + sha256: "4ed1d9bb36f7cd60aa6e6cd479779cc56a4cb4e4de8f49d487b1aaad831300fa" + url: "https://pub.dev" + source: hosted + version: "0.2.1+1" + image_picker_macos: + dependency: transitive + description: + name: image_picker_macos + sha256: "3f5ad1e8112a9a6111c46d0b57a7be2286a9a07fc6e1976fdf5be2bd31d4ff62" + url: "https://pub.dev" + source: hosted + version: "0.2.1+1" + image_picker_platform_interface: + dependency: transitive + description: + name: image_picker_platform_interface + sha256: "9ec26d410ff46f483c5519c29c02ef0e02e13a543f882b152d4bfd2f06802f80" + url: "https://pub.dev" + source: hosted + version: "2.10.0" + image_picker_windows: + dependency: transitive + description: + name: image_picker_windows + sha256: "6ad07afc4eb1bc25f3a01084d28520496c4a3bb0cb13685435838167c9dcedeb" + url: "https://pub.dev" + source: hosted + version: "0.2.1+1" + leak_tracker: + dependency: transitive + description: + name: leak_tracker + sha256: "7f0df31977cb2c0b88585095d168e689669a2cc9b97c309665e3386f3e9d341a" + url: "https://pub.dev" + source: hosted + version: "10.0.4" + leak_tracker_flutter_testing: + dependency: transitive + description: + name: leak_tracker_flutter_testing + sha256: "06e98f569d004c1315b991ded39924b21af84cf14cc94791b8aea337d25b57f8" + url: "https://pub.dev" + source: hosted + version: "3.0.3" + leak_tracker_testing: + dependency: transitive + description: + name: leak_tracker_testing + sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3" + url: "https://pub.dev" + source: hosted + version: "3.0.1" lints: dependency: transitive description: name: lints - sha256: "0a217c6c989d21039f1498c3ed9f3ed71b354e69873f13a8dfc3c9fe76f1b452" + sha256: "976c774dd944a42e83e2467f4cc670daef7eed6295b10b36ae8c85bcbf828235" url: "https://pub.dev" source: hosted - version: "2.1.1" + version: "4.0.0" matcher: dependency: transitive description: name: matcher - sha256: "1803e76e6653768d64ed8ff2e1e67bea3ad4b923eb5c56a295c3e634bad5960e" + sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb url: "https://pub.dev" source: hosted - version: "0.12.16" + version: "0.12.16+1" material_color_utilities: dependency: transitive description: name: material_color_utilities - sha256: "9528f2f296073ff54cb9fee677df673ace1218163c3bc7628093e7eed5203d41" + sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a" url: "https://pub.dev" source: hosted - version: "0.5.0" + version: "0.8.0" meta: dependency: transitive description: name: meta - sha256: a6e590c838b18133bb482a2745ad77c5bb7715fb0451209e1a7567d416678b8e + sha256: "7687075e408b093f36e6bbf6c91878cc0d4cd10f409506f7bc996f68220b9136" url: "https://pub.dev" source: hosted - version: "1.10.0" + version: "1.12.0" + mime: + dependency: transitive + description: + name: mime + sha256: "2e123074287cc9fd6c09de8336dae606d1ddb88d9ac47358826db698c176a1f2" + url: "https://pub.dev" + source: hosted + version: "1.0.5" mobile_scanner: dependency: transitive description: name: mobile_scanner - sha256: "2fbc3914fe625e196c64ea8ffc4084cd36781d2be276d4d5923b11af3b5d44ff" + sha256: b8c0e9afcfd52534f85ec666f3d52156f560b5e6c25b1e3d4fe2087763607926 url: "https://pub.dev" source: hosted - version: "3.4.1" + version: "5.1.1" path: dependency: transitive description: name: path - sha256: "8829d8a55c13fc0e37127c29fedf290c102f4e40ae94ada574091fe0ff96c917" + sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af" url: "https://pub.dev" source: hosted - version: "1.8.3" + version: "1.9.0" plugin_platform_interface: dependency: transitive description: name: plugin_platform_interface - sha256: da3fdfeccc4d4ff2da8f8c556704c08f912542c5fb3cf2233ed75372384a034d + sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02" url: "https://pub.dev" source: hosted - version: "2.1.6" + version: "2.1.8" sky_engine: dependency: transitive description: flutter @@ -264,10 +352,10 @@ packages: dependency: transitive description: name: test_api - sha256: "5c2f730018264d276c20e4f1503fd1308dfbbae39ec8ee63c5236311ac06954b" + sha256: "9955ae474176f7ac8ee4e989dadfb411a58c30415bcfb648fa04b2b8a03afa7f" url: "https://pub.dev" source: hosted - version: "0.6.1" + version: "0.7.0" typed_data: dependency: transitive description: @@ -284,14 +372,22 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.4" + vm_service: + dependency: transitive + description: + name: vm_service + sha256: "3923c89304b715fb1eb6423f017651664a03bf5f4b29983627c4da791f74a4ec" + url: "https://pub.dev" + source: hosted + version: "14.2.1" web: dependency: transitive description: name: web - sha256: afe077240a270dcfd2aafe77602b4113645af95d0ad31128cc02bce5ac5d5152 + sha256: "97da13628db363c635202ad97068d47c5b8aa555808e7a9411963c533b449b27" url: "https://pub.dev" source: hosted - version: "0.3.0" + version: "0.5.1" sdks: - dart: ">=3.2.0-194.0.dev <4.0.0" - flutter: ">=3.10.0" + dart: ">=3.3.0 <4.0.0" + flutter: ">=3.19.0" diff --git a/example/pubspec.yaml b/example/pubspec.yaml index f0e678b..ffd20dd 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -1,91 +1,27 @@ name: example description: A new Flutter project. -# The following line prevents the package from being accidentally published to -# pub.dev using `flutter pub publish`. This is preferred for private packages. publish_to: "none" # Remove this line if you wish to publish to pub.dev -# The following defines the version and build number for your application. -# A version number is three numbers separated by dots, like 1.2.43 -# followed by an optional build number separated by a +. -# Both the version and the builder number may be overridden in flutter -# build by specifying --build-name and --build-number, respectively. -# In Android, build-name is used as versionName while build-number used as versionCode. -# Read more about Android versioning at https://developer.android.com/studio/publish/versioning -# In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion. -# Read more about iOS versioning at -# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html version: 1.0.0+1 environment: sdk: ">=2.17.6 <3.0.0" -# Dependencies specify other packages that your package needs in order to work. -# To automatically upgrade your package dependencies to the latest versions -# consider running `flutter pub upgrade --major-versions`. Alternatively, -# dependencies can be manually updated by changing the version numbers below to -# the latest version available on pub.dev. To see which dependencies have newer -# versions available, run `flutter pub outdated`. dependencies: flutter: sdk: flutter ai_barcode_scanner: path: ../ - firebase_auth: ^4.2.6 - # The following adds the Cupertino Icons font to your application. - # Use with the CupertinoIcons class for iOS style icons. - cupertino_icons: ^1.0.2 + cupertino_icons: ^1.0.8 dev_dependencies: flutter_test: sdk: flutter - # The "flutter_lints" package below contains a set of recommended lints to - # encourage good coding practices. The lint set provided by the package is - # activated in the `analysis_options.yaml` file located at the root of your - # package. See that file for information about deactivating specific lint - # rules and activating additional ones. - flutter_lints: ^2.0.0 + flutter_lints: ^4.0.0 -# For information on the generic Dart part of this file, see the -# following page: https://dart.dev/tools/pub/pubspec - -# The following section is specific to Flutter packages. flutter: - # The following line ensures that the Material Icons font is - # included with your application, so that you can use the icons in - # the material Icons class. uses-material-design: true - - # To add assets to your application, add an assets section, like this: - # assets: - # - images/a_dot_burr.jpeg - # - images/a_dot_ham.jpeg - - # An image asset can refer to one or more resolution-specific "variants", see - # https://flutter.dev/assets-and-images/#resolution-aware - - # For details regarding adding assets from package dependencies, see - # https://flutter.dev/assets-and-images/#from-packages - - # To add custom fonts to your application, add a fonts section here, - # in this "flutter" section. Each entry in this list should have a - # "family" key with the font family name, and a "fonts" key with a - # list giving the asset and other descriptors for the font. For - # example: - # fonts: - # - family: Schyler - # fonts: - # - asset: fonts/Schyler-Regular.ttf - # - asset: fonts/Schyler-Italic.ttf - # style: italic - # - family: Trajan Pro - # fonts: - # - asset: fonts/TrajanPro.ttf - # - asset: fonts/TrajanPro_Bold.ttf - # weight: 700 - # - # For details regarding fonts from package dependencies, - # see https://flutter.dev/custom-fonts/#from-packages diff --git a/example/web/index.html b/example/web/index.html index 55ca817..957f108 100644 --- a/example/web/index.html +++ b/example/web/index.html @@ -1,5 +1,6 @@ + - + - + - example + flutter322 - - - - - + - + - + + \ No newline at end of file diff --git a/example/windows/flutter/generated_plugin_registrant.cc b/example/windows/flutter/generated_plugin_registrant.cc index d141b74..77ab7a0 100644 --- a/example/windows/flutter/generated_plugin_registrant.cc +++ b/example/windows/flutter/generated_plugin_registrant.cc @@ -6,12 +6,9 @@ #include "generated_plugin_registrant.h" -#include -#include +#include void RegisterPlugins(flutter::PluginRegistry* registry) { - FirebaseAuthPluginCApiRegisterWithRegistrar( - registry->GetRegistrarForPlugin("FirebaseAuthPluginCApi")); - FirebaseCorePluginCApiRegisterWithRegistrar( - registry->GetRegistrarForPlugin("FirebaseCorePluginCApi")); + FileSelectorWindowsRegisterWithRegistrar( + registry->GetRegistrarForPlugin("FileSelectorWindows")); } diff --git a/example/windows/flutter/generated_plugins.cmake b/example/windows/flutter/generated_plugins.cmake index 29944d5..a423a02 100644 --- a/example/windows/flutter/generated_plugins.cmake +++ b/example/windows/flutter/generated_plugins.cmake @@ -3,8 +3,7 @@ # list(APPEND FLUTTER_PLUGIN_LIST - firebase_auth - firebase_core + file_selector_windows ) list(APPEND FLUTTER_FFI_PLUGIN_LIST diff --git a/lib/src/ai_barcode_scanner.dart b/lib/src/ai_barcode_scanner.dart index d6f6dcf..d875ccf 100644 --- a/lib/src/ai_barcode_scanner.dart +++ b/lib/src/ai_barcode_scanner.dart @@ -1,26 +1,14 @@ -import 'dart:developer'; - import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:mobile_scanner/mobile_scanner.dart'; +import 'draggable_sheet.dart'; +import 'error_builder.dart'; +import 'gallery_button.dart'; import 'overlay.dart'; /// Barcode scanner widget class AiBarcodeScanner extends StatefulWidget { - /// Function that gets Called when barcode is scanned successfully - /// - final void Function(String) onScan; - - /// Function that gets called when a Barcode is detected. - /// - /// [barcode] The barcode object with all information about the scanned code. - /// [args] Information about the state of the MobileScanner widget - final void Function(BarcodeCapture)? onDetect; - - /// Validate barcode text with a function - final bool Function(String value)? validator; - /// Fit to screen final BoxFit fit; @@ -31,7 +19,7 @@ class AiBarcodeScanner extends StatefulWidget { final bool showOverlay; /// Overlay border color (default: white) - final Color borderColor; + final Color? borderColor; /// Overlay border width (default: 10) final double borderWidth; @@ -57,17 +45,6 @@ class AiBarcodeScanner extends StatefulWidget { /// Overlay cut out size (default: 300) final double cutOutSize; - /// Hint widget (optional) (default: Text('Scan QR Code')) - /// Hint widget will be replaced the bottom of the screen. - /// If you want to replace the bottom screen widget, use [bottomBar] - final Widget? bottomBar; - - /// Hint text (default: 'Scan QR Code') - final String bottomBarText; - - /// Hint text style - final TextStyle bottomBarTextStyle; - /// Show error or not (default: true) final bool showError; @@ -80,9 +57,6 @@ class AiBarcodeScanner extends StatefulWidget { /// Success color (default: green) final Color successColor; - /// Can auto back to previous page when barcode is successfully scanned (default: true) - final bool canPop; - /// The function that builds an error widget when the scanner /// could not be started. /// @@ -97,63 +71,152 @@ class AiBarcodeScanner extends StatefulWidget { /// If this is null, a black [ColoredBox] is used as placeholder. final Widget Function(BuildContext, Widget?)? placeholderBuilder; - /// The function that signals when the barcode scanner is started. - final void Function(MobileScannerArguments?)? onScannerStarted; - /// Called when this object is removed from the tree permanently. final void Function()? onDispose; - /// if set barcodes will only be scanned if they fall within this [Rect] - /// useful for having a cut-out overlay for example. these [Rect] - /// coordinates are relative to the widget size, so by how much your - /// rectangle overlays the actual image can depend on things like the - /// [BoxFit] + /// AppBar widget + /// you can use this to add appBar to the scanner screen + /// + final PreferredSizeWidget? appBar; + + /// The builder for the overlay above the camera preview. + /// + /// The resulting widget can be combined with the [scanWindow] rectangle + /// to create a cutout for the camera preview. + /// + /// The [BoxConstraints] for this builder + /// are the same constraints that are used to compute the effective [scanWindow]. + /// + /// The overlay is only displayed when the camera preview is visible. + final LayoutWidgetBuilder? overlayBuilder; + + /// The scan window rectangle for the barcode scanner. + /// + /// If this is not null, the barcode scanner will only scan barcodes + /// which intersect this rectangle. + /// + /// This rectangle is relative to the layout size + /// of the *camera preview widget* in the widget tree, + /// rather than the actual size of the camera preview output. + /// This is because the size of the camera preview widget + /// might not be the same as the size of the camera output. + /// + /// For example, the applied [fit] has an effect on the size of the camera preview widget, + /// while the camera preview size remains the same. + /// + /// The following example shows a scan window that is centered, + /// fills half the height and one third of the width of the layout: + /// + /// ```dart + /// LayoutBuider( + /// builder: (BuildContext context, BoxConstraints constraints) { + /// final Size layoutSize = constraints.biggest; + /// + /// final double scanWindowWidth = layoutSize.width / 3; + /// final double scanWindowHeight = layoutSize.height / 2; + /// + /// final Rect scanWindow = Rect.fromCenter( + /// center: layoutSize.center(Offset.zero), + /// width: scanWindowWidth, + /// height: scanWindowHeight, + /// ); + /// } + /// ); + /// ``` final Rect? scanWindow; - /// Only set this to true if you are starting another instance of mobile_scanner - /// right after disposing the first one, like in a PageView. + /// The threshold for updates to the [scanWindow]. + /// + /// If the [scanWindow] would be updated, + /// due to new layout constraints for the scanner, + /// and the width or height of the new scan window have not changed by this threshold, + /// then the scan window is not updated. + /// + /// It is recommended to set this threshold + /// if scan window updates cause performance issues. + /// + /// Defaults to no threshold for scan window updates. /// - /// Default: false - final bool? startDelay; + final void Function(BarcodeCapture)? onDetect; - /// Appbar widget - /// you can use this to add appbar to the scanner screen + /// The threshold for updates to the [scanWindow]. /// - final PreferredSizeWidget? appBar; + /// If the [scanWindow] would be updated, + /// due to new layout constraints for the scanner, + /// and the width or height of the new scan window have not changed by this threshold, + /// then the scan window is not updated. + /// + /// It is recommended to set this threshold + /// if scan window updates cause performance issues. + /// + /// Defaults to no threshold for scan window updates. + final double scanWindowUpdateThreshold; + /// Validator function to check if barcode is valid or not + final bool Function(BarcodeCapture)? validator; + + final void Function(String?)? onImagePick; + + /// Title for the draggable sheet (default: 'Scan any QR code') + final String title; + + /// Child widget for the draggable sheet (default: SizedBox.shrink()) + final Widget child; + + /// Hide drag handler of the draggable sheet (default: false) + final bool hideDragHandler; + + /// Hide title of the draggable sheet (default: false) + final bool hideTitle; + + /// Upload from gallery button alignment + /// default: bottom center, center, 0.75 + final AlignmentGeometry? buttonAlignment; + + /// actions for the app bar (optional) + /// Camera switch and torch toggle buttons are added by default + /// You can add more actions to the app bar using this parameter + final List? actions; + + /// Optional function to be called when clicking the back button on the app bar + /// If not provided, the default behavior is to pop the current route from the navigator + final void Function()? onPop; const AiBarcodeScanner({ - Key? key, - required this.onScan, - this.validator, + super.key, this.fit = BoxFit.cover, this.controller, - this.onDetect, - this.borderColor = Colors.white, - this.borderWidth = 10, - this.overlayColor = const Color.fromRGBO(0, 0, 0, 80), - this.borderRadius = 10, - this.borderLength = 40, - this.cutOutSize = 300, + this.borderColor, this.cutOutWidth, this.cutOutHeight, - this.cutOutBottomOffset = 0, - this.bottomBarText = 'Scan QR Code', - this.bottomBarTextStyle = const TextStyle(fontWeight: FontWeight.bold), + this.borderWidth = 12, + this.overlayColor = const Color.fromRGBO(0, 0, 0, 82), + this.borderRadius = 24, + this.borderLength = 42, + this.cutOutSize = 352, + this.cutOutBottomOffset = 124, + this.scanWindowUpdateThreshold = 0.0, this.showOverlay = true, this.showError = true, - this.errorColor = Colors.red, this.showSuccess = true, + this.errorColor = Colors.red, this.successColor = Colors.green, - this.canPop = true, this.errorBuilder, this.placeholderBuilder, - this.onScannerStarted, this.onDispose, this.scanWindow, - this.startDelay, - this.bottomBar, this.appBar, - }) : super(key: key); + this.overlayBuilder, + this.onDetect, + this.validator, + this.onImagePick, + this.title = 'Scan any QR code', + this.child = const SizedBox.shrink(), + this.hideDragHandler = false, + this.hideTitle = false, + this.buttonAlignment, + this.actions, + this.onPop, + }); @override State createState() => _AiBarcodeScannerState(); @@ -161,211 +224,158 @@ class AiBarcodeScanner extends StatefulWidget { class _AiBarcodeScannerState extends State { /// bool to check if barcode is valid or not - bool? _isSuccess; + final ValueNotifier _isSuccess = ValueNotifier(null); /// Scanner controller late MobileScannerController controller; + double _cutOutBottomOffset = 0; + @override void initState() { controller = widget.controller ?? MobileScannerController(); + _cutOutBottomOffset = widget.cutOutBottomOffset; super.initState(); } @override void dispose() { controller.dispose(); + widget.controller?.dispose(); + widget.onDispose?.call(); super.dispose(); - - /// calls onDispose function if it is not null - if (widget.onDispose != null) { - widget.onDispose!.call(); - } } @override Widget build(BuildContext context) { - // /// keeps the app in portrait mode - // SystemChrome.setPreferredOrientations([ - // DeviceOrientation.portraitUp, - // ]); return OrientationBuilder( builder: (context, orientation) { + final isLandscape = orientation == Orientation.landscape; + if (isLandscape) { + _cutOutBottomOffset = 0; + } else { + _cutOutBottomOffset = widget.cutOutBottomOffset; + } return Scaffold( - bottomNavigationBar: orientation == Orientation.portrait - ? widget.bottomBar ?? - ListTile( - leading: Builder( - builder: (context) { - return IconButton( - tooltip: "Switch Camera", - onPressed: () => controller.switchCamera(), - icon: ValueListenableBuilder( - valueListenable: controller.cameraFacingState, - builder: (context, state, child) { - switch (state) { - case CameraFacing.front: - return const Icon( - Icons.camera_front); - case CameraFacing.back: - return const Icon( - Icons.camera_rear); - } - }, - ), - ); - }, - ), - title: Text( - widget.bottomBarText, - textAlign: TextAlign.center, - style: widget.bottomBarTextStyle, - ), - trailing: Builder( - builder: (context) { - return IconButton( - tooltip: "Torch", - onPressed: () => controller.toggleTorch(), - icon: ValueListenableBuilder( - valueListenable: controller.torchState, - builder: (context, state, child) { - switch (state) { - case TorchState.off: - return const Icon( - Icons.flash_off); - case TorchState.on: - return const Icon( - Icons.flash_on); - } - }, - ), - ); - }, + appBar: widget.appBar ?? + AppBar( + leading: Navigator.canPop(context) + ? IconButton( + icon: const Icon(Icons.close_rounded), + onPressed: widget.onPop ?? () => Navigator.pop(context), + ) + : null, + backgroundColor: Colors.transparent, + foregroundColor: Colors.white, + elevation: 0, + actions: [ + IconButton( + icon: const Icon(Icons.cameraswitch_rounded), + onPressed: () => controller.switchCamera(), + ), + IconButton( + icon: controller.torchEnabled + ? const Icon(Icons.flashlight_off_rounded) + : const Icon(Icons.flashlight_on_rounded), + onPressed: () => controller.toggleTorch(), + ), + if (isLandscape) + GalleryButton.icon( + onImagePick: widget.onImagePick, + onDetect: widget.onDetect, + validator: widget.validator, + controller: controller, + isSuccess: _isSuccess, ), - ) - : null, - appBar: widget.appBar, + ...?widget.actions, + ], + ), + extendBodyBehindAppBar: true, + bottomSheet: isLandscape + ? null + : DraggableSheet( + title: widget.title, + hideDragHandler: widget.hideDragHandler, + hideTitle: widget.hideTitle, + child: widget.child, + ), body: Stack( - children: [ MobileScanner( + onDetect: onDetect, controller: controller, fit: widget.fit, - errorBuilder: widget.errorBuilder, - onScannerStarted: widget.onScannerStarted, + errorBuilder: widget.errorBuilder ?? + (_, error, ___) => const ErrorBuilder(), placeholderBuilder: widget.placeholderBuilder, scanWindow: widget.scanWindow, - startDelay: widget.startDelay ?? false, key: widget.key, - onDetect: (BarcodeCapture barcode) async { - widget.onDetect?.call(barcode); - - if (barcode.barcodes.isEmpty) { - log('Scanned Code is Empty'); - return; - } - - final String code = barcode.barcodes.first.rawValue ?? ""; - - if ((widget.validator != null && !widget.validator!(code))) { - setState(() { - HapticFeedback.heavyImpact(); - log('Invalid Barcode => $code'); - _isSuccess = false; - }); - return; - } - setState(() { - _isSuccess = true; - HapticFeedback.lightImpact(); - log('Barcode rawValue => $code'); - widget.onScan(code); - }); - if (widget.canPop && mounted && Navigator.canPop(context)) { - Navigator.pop(context); - return; - } - }, + overlayBuilder: widget.overlayBuilder, + scanWindowUpdateThreshold: widget.scanWindowUpdateThreshold, ), if (widget.showOverlay) - Container( - decoration: ShapeDecoration( - shape: OverlayShape( - borderRadius: widget.borderRadius, - borderColor: ((_isSuccess ?? false) && widget.showSuccess) - ? widget.successColor - : (!(_isSuccess ?? true) && widget.showError) - ? widget.errorColor - : widget.borderColor, - borderLength: widget.borderLength, - borderWidth: widget.borderWidth, - cutOutSize: widget.cutOutSize, - cutOutBottomOffset: widget.cutOutBottomOffset, - cutOutWidth: widget.cutOutWidth, - cutOutHeight: widget.cutOutHeight, - overlayColor: - ((_isSuccess ?? false) && widget.showSuccess) - ? widget.successColor.withOpacity(0.4) - : (!(_isSuccess ?? true) && widget.showError) - ? widget.errorColor.withOpacity(0.4) - : widget.overlayColor, - ), - ), - ), - if(orientation == Orientation.landscape) - Align( - alignment: Alignment.centerRight, - child: Container( - padding: const EdgeInsets.all(12), - decoration: const BoxDecoration( - color: Colors.white, - shape: BoxShape.rectangle,), - - child: Column( - mainAxisAlignment: MainAxisAlignment.spaceAround, - children: [ - IconButton( - tooltip: "Switch Camera", - onPressed: () => controller.switchCamera(), - icon: ValueListenableBuilder( - valueListenable: controller.cameraFacingState, - builder: (context, state, child) { - switch (state) { - case CameraFacing.front: - return const Icon( - Icons.camera_front); - case CameraFacing.back: - return const Icon( - Icons.camera_rear); - } - }, - ), - ), - IconButton( - tooltip: "Torch", - onPressed: () => controller.toggleTorch(), - icon: ValueListenableBuilder( - valueListenable: controller.torchState, - builder: (context, state, child) { - switch (state) { - case TorchState.off: - return const Icon( - Icons.flash_off); - case TorchState.on: - return const Icon( - Icons.flash_on); - } - }, + ValueListenableBuilder( + valueListenable: _isSuccess, + builder: (context, isSuccess, __) { + return Container( + decoration: ShapeDecoration( + shape: OverlayShape( + borderRadius: widget.borderRadius, + borderColor: + ((isSuccess ?? false) && widget.showSuccess) + ? widget.successColor + : (!(isSuccess ?? true) && widget.showError) + ? widget.errorColor + : widget.borderColor ?? Colors.white, + borderLength: widget.borderLength, + borderWidth: widget.borderWidth, + cutOutSize: widget.cutOutSize, + cutOutBottomOffset: _cutOutBottomOffset, + cutOutWidth: widget.cutOutWidth, + cutOutHeight: widget.cutOutHeight, + overlayColor: + ((isSuccess ?? false) && widget.showSuccess) + ? widget.successColor.withOpacity(0.4) + : (!(isSuccess ?? true) && widget.showError) + ? widget.errorColor.withOpacity(0.4) + : widget.overlayColor, ), ), - ], + ); + }, + ), + if (!isLandscape) + Align( + alignment: widget.buttonAlignment ?? + Alignment.lerp( + Alignment.bottomCenter, + Alignment.center, + 0.75, + )!, + child: GalleryButton( + onImagePick: widget.onImagePick, + onDetect: widget.onDetect, + validator: widget.validator, + controller: controller, + isSuccess: _isSuccess, ), ), - ) ], ), ); }, ); } + + void onDetect(BarcodeCapture barcodes) { + widget.onDetect?.call(barcodes); + if (widget.validator != null) { + final isValid = widget.validator!(barcodes); + if (!isValid) { + HapticFeedback.heavyImpact(); + } + HapticFeedback.mediumImpact(); + _isSuccess.value = isValid; + } + } } diff --git a/lib/src/draggable_sheet.dart b/lib/src/draggable_sheet.dart new file mode 100644 index 0000000..4a0a1dd --- /dev/null +++ b/lib/src/draggable_sheet.dart @@ -0,0 +1,61 @@ +import 'package:flutter/material.dart'; + +/// A draggable sheet that can be used to show any widget +/// with a title and a drag handler. +/// +class DraggableSheet extends StatelessWidget { + final String title; + final Widget child; + final bool hideDragHandler; + final bool hideTitle; + DraggableSheet({ + super.key, + this.title = 'Scan any QR code', + this.child = const SizedBox.shrink(), + this.hideDragHandler = false, + this.hideTitle = false, + }); + final _controller = DraggableScrollableController(); + @override + Widget build(BuildContext context) { + return DraggableScrollableSheet( + controller: _controller, + initialChildSize: 0.1, + minChildSize: 0.1, + maxChildSize: 0.4, + expand: false, + builder: (BuildContext context, ScrollController scrollController) { + return Column( + children: [ + ...[ + if (!hideDragHandler) + Container( + decoration: BoxDecoration( + color: Theme.of(context).dividerColor, + borderRadius: BorderRadius.circular(10), + ), + margin: const EdgeInsets.symmetric(vertical: 10), + child: const SizedBox( + width: 40, + height: 5, + ), + ), + if (!hideTitle) ...[ + const SizedBox(height: 6), + Text( + title, + style: Theme.of(context).textTheme.titleLarge, + ), + const SizedBox(height: 6), + const Divider(), + ] + ], + Expanded( + child: child, + ), + ], + ); + }, + ); + } +} diff --git a/lib/src/error_builder.dart b/lib/src/error_builder.dart new file mode 100644 index 0000000..601a992 --- /dev/null +++ b/lib/src/error_builder.dart @@ -0,0 +1,32 @@ +import 'package:flutter/material.dart'; + +/// A builder that shows an error message when the camera fails to start. +class ErrorBuilder extends StatelessWidget { + const ErrorBuilder({super.key}); + + @override + Widget build(BuildContext context) { + return Builder( + builder: (context) { + return Center( + heightFactor: 6.4, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + mainAxisSize: MainAxisSize.min, + children: [ + const Icon( + Icons.no_photography_rounded, + size: 68, + ), + const SizedBox(height: 12), + Text( + "Could not start the camera", + style: Theme.of(context).textTheme.titleLarge, + ), + ], + ), + ); + }, + ); + } +} diff --git a/lib/src/gallery_button.dart b/lib/src/gallery_button.dart new file mode 100644 index 0000000..27b4c9a --- /dev/null +++ b/lib/src/gallery_button.dart @@ -0,0 +1,74 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:image_picker/image_picker.dart'; + +import '../ai_barcode_scanner.dart'; + +/// A button that allows the user to pick an image from the gallery +/// and analyze it for barcodes & QR codes. +class GalleryButton extends StatelessWidget { + final void Function(String?)? onImagePick; + final void Function(BarcodeCapture)? onDetect; + final bool Function(BarcodeCapture)? validator; + final MobileScannerController controller; + final ValueNotifier isSuccess; + final String child; + + const GalleryButton({ + super.key, + this.onImagePick, + this.onDetect, + this.validator, + required this.controller, + required this.isSuccess, + this.child = 'Gallery', + }); + + const GalleryButton.icon({ + super.key, + this.onImagePick, + this.onDetect, + this.validator, + required this.controller, + required this.isSuccess, + this.child = 'Icon', + }); + + @override + Widget build(BuildContext context) { + Future pickImage() async { + final ImagePicker picker = ImagePicker(); + final XFile? image = await picker.pickImage(source: ImageSource.gallery); + onImagePick?.call(image?.path); + if (image != null) { + final barcodes = await controller.analyzeImage(image.path); + if (barcodes != null) { + HapticFeedback.mediumImpact(); + onDetect?.call(barcodes); + if (validator != null) { + final isValid = validator!(barcodes); + if (!isValid) { + HapticFeedback.heavyImpact(); + } + HapticFeedback.mediumImpact(); + isSuccess.value = isValid; + } + } + } + } + + switch (child) { + case 'Icon': + return IconButton( + icon: const Icon(Icons.image), + onPressed: pickImage, + ); + default: + return FilledButton.icon( + onPressed: pickImage, + label: const Text("Upload from Gallery"), + icon: const Icon(Icons.image_rounded), + ); + } + } +} diff --git a/pubspec.yaml b/pubspec.yaml index 350c126..27827d3 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: ai_barcode_scanner description: A universal AI 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.4.2 +version: 5.1.1 homepage: https://mohesu.com repository: https://github.com/mohesu/barcode_scanner.git issue_tracker: https://github.com/mohesu/barcode_scanner/issues @@ -17,17 +17,18 @@ topics: environment: sdk: ">=2.17.0 <4.0.0" - flutter: ">=3.10.0" + flutter: ">=3.10.0 <4.0.0" dependencies: flutter: sdk: flutter - mobile_scanner: ^3.5.5 + mobile_scanner: ^5.1.1 + image_picker: ^1.1.1 dev_dependencies: flutter_test: sdk: flutter - flutter_lints: ^3.0.1 + flutter_lints: ^4.0.0 flutter: