diff --git a/.github/workflows/flutter.yml b/.github/workflows/flutter.yml
index 2a88003ba..48f055a7c 100644
--- a/.github/workflows/flutter.yml
+++ b/.github/workflows/flutter.yml
@@ -36,7 +36,7 @@ jobs:
- uses: subosito/flutter-action@v2.12.0
with:
cache: true
- flutter-version: '3.13'
+ flutter-version: '3.19'
channel: 'stable'
- name: Version
run: flutter doctor -v
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 40082b8e9..27562c9fb 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,9 +1,39 @@
+## 5.0.0-beta.1
+
+**BREAKING CHANGES:**
+
+* Flutter 3.19.0 is now required.
+* The `width` and `height` of `BarcodeCapture` have been removed, in favor of `size`.
+* The `raw` attribute is now `Object?` instead of `dynamic`, so that it participates in type promotion.
+* The `MobileScannerArguments` class has been removed from the public API, as it is an internal type.
+* The `cameraFacingOverride` named argument for the `start()` method has been renamed to `cameraDirection`.
+* The `analyzeImage` function now correctly returns a `BarcodeCapture?` instead of a boolean.
+* The `formats` attribute of the `MobileScannerController` is now non-null.
+* The `MobileScannerState` enum has been renamed to `MobileScannerAuthorizationState`.
+* The various `ValueNotifier`s for the camera state have been removed. Use the `value` of the `MobileScannerController` instead.
+* The `hasTorch` getter has been removed. Instead, use the torch state of the controller's value.
+ The `TorchState` enum now provides a new value for unavailable flashlights.
+* The `autoStart` attribute has been removed from the `MobileScannerController`. The controller should be manually started on-demand.
+* A controller is now required for the `MobileScanner` widget.
+* The `onPermissionSet`, `onStart` and `onScannerStarted` methods have been removed from the `MobileScanner` widget. Instead, await `MobileScannerController.start()`.
+* The `startDelay` has been removed from the `MobileScanner` widget. Instead, use a delay between manual starts of one or more controllers.
+* The `onDetect` method has been removed from the `MobileScanner` widget. Instead, listen to `MobileScannerController.barcodes` directly.
+* The `overlay` widget of the `MobileScanner` has been replaced by a new property, `overlayBuilder`, which provides the constraints for the overlay.
+* The torch can no longer be toggled on the web, as this is only available for image tracks and not video tracks. As a result the torch state for the web will always be `TorchState.unavailable`.
+* The zoom scale can no longer be modified on the web, as this is only available for image tracks and not video tracks. As a result, the zoom scale will always be `1.0`.
+
+Improvements:
+* The `MobileScannerController` is now a ChangeNotifier, with `MobileScannerState` as its model.
+* The web implementation now supports alternate URLs for loading the barcode library.
+
## 4.0.1
Bugs fixed:
* [iOS] Fixed a crash with a nil capture session when starting the camera. (thanks @navaronbracke !)
## 4.0.0
-BREAKING CHANGES:
+
+**BREAKING CHANGES:**
+
* [Android] compileSdk has been upgraded to version 34.
* [Android] Java version has been upgraded to version 17.
@@ -186,7 +216,8 @@ Deprecated:
* The `onStart` method has been renamed to `onScannerStarted`.
* The `onPermissionSet` argument of the `MobileScannerController` is now deprecated.
-Breaking changes:
+**BREAKING CHANGES:**
+
* `MobileScannerException` now uses an `errorCode` instead of a `message`.
* `MobileScannerException` now contains additional details from the original error.
* Refactored `MobileScannerController.start()` to throw `MobileScannerException`s
@@ -223,7 +254,9 @@ Fixes:
* [iOS] Fix crash when changing torch state
## 3.0.0-beta.2
-Breaking changes:
+
+**BREAKING CHANGES:**
+
* The arguments parameter of onDetect is removed. The data is now returned by the onStart callback
in the MobileScanner widget.
* onDetect now returns the object BarcodeCapture, which contains a List of barcodes and, if enabled, an image.
@@ -243,7 +276,9 @@ Other improvements:
* [iOS] Updated POD dependencies
## 3.0.0-beta.1
-Breaking changes:
+
+**BREAKING CHANGES:**
+
* [Android] SDK updated to SDK 33.
Features:
@@ -259,7 +294,9 @@ Other changes:
* Several minor code improvements
## 2.0.0
-Breaking changes:
+
+**BREAKING CHANGES:**
+
This version is only compatible with flutter 3.0.0 and later.
## 1.1.2-play-services
@@ -293,7 +330,9 @@ Bugfixes:
* Upgraded several dependencies.
## 1.0.0
-BREAKING CHANGES:
+
+**BREAKING CHANGES:**
+
This version adds a new allowDuplicates option which now defaults to FALSE. this means that it will only call onDetect once after a scan.
If you still want duplicates, you can set allowDuplicates to true.
This also means that you don't have to check for duplicates yourself anymore.
diff --git a/README.md b/README.md
index bfb33dc22..109fc04ea 100644
--- a/README.md
+++ b/README.md
@@ -7,17 +7,15 @@
A universal scanner for Flutter based on MLKit. Uses CameraX on Android and AVFoundation on iOS.
-
## 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: |
-| barcodeOverlay | :heavy_check_mark: | :heavy_check_mark: | :x: | :x: |
+| 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: | :heavy_check_mark: | :x: |
## Platform Support
@@ -26,6 +24,7 @@ See the example app for detailed implementation information.
| ✔ | ✔ | ✔ | ✔ | :x: | :x: |
## Platform specific setup
+
### Android
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.
@@ -61,194 +60,110 @@ Ensure that you granted camera permission in XCode -> Signing & Capabilities:
## Web
-This package uses ZXing on web to read barcodes so it needs to be included in `index.html` as script.
-```html
-
-```
-## Usage
+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.
-Import `package:mobile_scanner/mobile_scanner.dart`, and use the widget with or without the controller.
+### Providing a mirror for the barcode scanning library
-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:
+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';
- @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}');
- }
- },
- ),
- );
- }
+final String scriptUrl = // ...
+
+if (kIsWeb) {
+ MobileScannerPlatform.instance.setBarcodeLibraryScriptUrl(scriptUrl);
+}
```
-Example with controller and initial values:
+## Usage
+
+Import the package with `package:mobile_scanner/mobile_scanner.dart`.
+
+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,
- 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}');
- }
- },
- ),
- );
- }
+StreamSubscription? _subscription;
```
-Example with controller and torch & camera controls:
+Ensure that your `State` class mixes in `WidgetsBindingObserver`, to handle lifecyle changes:
```dart
-import 'package:mobile_scanner/mobile_scanner.dart';
-
- MobileScannerController cameraController = MobileScannerController();
+class MyState extends State with WidgetsBindingObserver {
+ // ...
@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}');
- }
- },
- ),
- );
+ void didChangeAppLifecycleState(AppLifecycleState state) {
+ super.didChangeAppLifecycleState(state);
+
+ 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 returning images
+Then, start the scanner in `void initState()`:
```dart
-import 'package:mobile_scanner/mobile_scanner.dart';
+@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());
+}
+```
- @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);
- });
- }
- },
- ),
- );
- }
+Finally, dispose of the the `MobileScannerController` when you are done with it.
+
+```dart
+@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
-
-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 |
-| displayValue | String? | |
-| type | BarcodeType | |
-| calendarEvent | CalendarEvent? | |
-| contactInfo | ContactInfo? | |
-| driverLicense | DriverLicense? | |
-| email | Email? | |
-| geoPoint | GeoPoint? | |
-| phone | Phone? | |
-| sms | SMS? | |
-| url | UrlBookmark? | |
-| wifi | WiFi? | WiFi Access-Point details |
+To display the camera preview, pass the controller to a `MobileScanner` widget.
+
+See the examples for runnable examples of various usages,
+such as the basic usage, applying a scan window, or retrieving images from the barcodes.
diff --git a/android/src/main/kotlin/dev/steenbakker/mobile_scanner/MobileScanner.kt b/android/src/main/kotlin/dev/steenbakker/mobile_scanner/MobileScanner.kt
index cbd1b1127..7cd091132 100644
--- a/android/src/main/kotlin/dev/steenbakker/mobile_scanner/MobileScanner.kt
+++ b/android/src/main/kotlin/dev/steenbakker/mobile_scanner/MobileScanner.kt
@@ -78,7 +78,7 @@ class MobileScanner(
scanner.process(inputImage)
.addOnSuccessListener { barcodes ->
if (detectionSpeed == DetectionSpeed.NO_DUPLICATES) {
- val newScannedBarcodes = barcodes.mapNotNull({ barcode -> barcode.rawValue }).sorted()
+ val newScannedBarcodes = barcodes.mapNotNull { barcode -> barcode.rawValue }.sorted()
if (newScannedBarcodes == lastScanned) {
// New scanned is duplicate, returning
return@addOnSuccessListener
@@ -424,7 +424,7 @@ class MobileScanner(
/**
* Analyze a single image.
*/
- fun analyzeImage(image: Uri, analyzerCallback: AnalyzerCallback) {
+ fun analyzeImage(image: Uri, onSuccess: AnalyzerSuccessCallback, onError: AnalyzerErrorCallback) {
val inputImage = InputImage.fromFilePath(activity, image)
scanner.process(inputImage)
@@ -432,15 +432,13 @@ class MobileScanner(
val barcodeMap = barcodes.map { barcode -> barcode.data }
if (barcodeMap.isNotEmpty()) {
- analyzerCallback(barcodeMap)
+ onSuccess(barcodeMap)
} else {
- analyzerCallback(null)
+ onSuccess(null)
}
}
.addOnFailureListener { e ->
- mobileScannerErrorCallback(
- e.localizedMessage ?: e.toString()
- )
+ onError(e.localizedMessage ?: e.toString())
}
}
diff --git a/android/src/main/kotlin/dev/steenbakker/mobile_scanner/MobileScannerCallbacks.kt b/android/src/main/kotlin/dev/steenbakker/mobile_scanner/MobileScannerCallbacks.kt
index f8549b3a2..5732a28ed 100644
--- a/android/src/main/kotlin/dev/steenbakker/mobile_scanner/MobileScannerCallbacks.kt
+++ b/android/src/main/kotlin/dev/steenbakker/mobile_scanner/MobileScannerCallbacks.kt
@@ -3,7 +3,8 @@ package dev.steenbakker.mobile_scanner
import dev.steenbakker.mobile_scanner.objects.MobileScannerStartParameters
typealias MobileScannerCallback = (barcodes: List>, image: ByteArray?, width: Int?, height: Int?) -> Unit
-typealias AnalyzerCallback = (barcodes: List>?) -> Unit
+typealias AnalyzerErrorCallback = (message: String) -> Unit
+typealias AnalyzerSuccessCallback = (barcodes: List>?) -> Unit
typealias MobileScannerErrorCallback = (error: String) -> Unit
typealias TorchStateCallback = (state: Int) -> Unit
typealias ZoomScaleStateCallback = (zoomScale: Double) -> Unit
diff --git a/android/src/main/kotlin/dev/steenbakker/mobile_scanner/MobileScannerHandler.kt b/android/src/main/kotlin/dev/steenbakker/mobile_scanner/MobileScannerHandler.kt
index b23a2d200..17299ebd3 100644
--- a/android/src/main/kotlin/dev/steenbakker/mobile_scanner/MobileScannerHandler.kt
+++ b/android/src/main/kotlin/dev/steenbakker/mobile_scanner/MobileScannerHandler.kt
@@ -26,16 +26,19 @@ class MobileScannerHandler(
private val addPermissionListener: (RequestPermissionsResultListener) -> Unit,
textureRegistry: TextureRegistry): MethodChannel.MethodCallHandler {
- private val analyzerCallback: AnalyzerCallback = { barcodes: List>?->
- if (barcodes != null) {
- barcodeHandler.publishEvent(mapOf(
- "name" to "barcode",
- "data" to barcodes
- ))
+ private val analyzeImageErrorCallback: AnalyzerErrorCallback = {
+ Handler(Looper.getMainLooper()).post {
+ analyzerResult?.error("MobileScanner", it, null)
+ analyzerResult = null
}
+ }
+ private val analyzeImageSuccessCallback: AnalyzerSuccessCallback = {
Handler(Looper.getMainLooper()).post {
- analyzerResult?.success(barcodes != null)
+ analyzerResult?.success(mapOf(
+ "name" to "barcode",
+ "data" to it
+ ))
analyzerResult = null
}
}
@@ -236,7 +239,8 @@ class MobileScannerHandler(
private fun analyzeImage(call: MethodCall, result: MethodChannel.Result) {
analyzerResult = result
val uri = Uri.fromFile(File(call.arguments.toString()))
- mobileScanner!!.analyzeImage(uri, analyzerCallback)
+
+ mobileScanner!!.analyzeImage(uri, analyzeImageSuccessCallback, analyzeImageErrorCallback)
}
private fun toggleTorch(call: MethodCall, result: MethodChannel.Result) {
@@ -265,7 +269,7 @@ class MobileScannerHandler(
}
private fun updateScanWindow(call: MethodCall, result: MethodChannel.Result) {
- mobileScanner!!.scanWindow = call.argument?>("rect")
+ mobileScanner?.scanWindow = call.argument?>("rect")
result.success(null)
}
diff --git a/example/lib/barcode_list_scanner_controller.dart b/example/lib/barcode_list_scanner_controller.dart
deleted file mode 100644
index e1a4f39aa..000000000
--- a/example/lib/barcode_list_scanner_controller.dart
+++ /dev/null
@@ -1,192 +0,0 @@
-import 'package:flutter/material.dart';
-import 'package:image_picker/image_picker.dart';
-import 'package:mobile_scanner/mobile_scanner.dart';
-import 'package:mobile_scanner_example/scanner_error_widget.dart';
-
-class BarcodeListScannerWithController extends StatefulWidget {
- const BarcodeListScannerWithController({super.key});
-
- @override
- State createState() =>
- _BarcodeListScannerWithControllerState();
-}
-
-class _BarcodeListScannerWithControllerState
- extends State
- with SingleTickerProviderStateMixin {
- BarcodeCapture? barcodeCapture;
-
- final MobileScannerController controller = MobileScannerController(
- torchEnabled: true,
- // formats: [BarcodeFormat.qrCode]
- // facing: CameraFacing.front,
- // detectionSpeed: DetectionSpeed.normal
- // detectionTimeoutMs: 1000,
- // returnImage: false,
- );
-
- bool isStarted = true;
-
- void _startOrStop() {
- try {
- if (isStarted) {
- controller.stop();
- } else {
- controller.start();
- }
- setState(() {
- isStarted = !isStarted;
- });
- } on Exception catch (e) {
- ScaffoldMessenger.of(context).showSnackBar(
- SnackBar(
- content: Text('Something went wrong! $e'),
- backgroundColor: Colors.red,
- ),
- );
- }
- }
-
- @override
- Widget build(BuildContext context) {
- return Scaffold(
- appBar: AppBar(title: const Text('With ValueListenableBuilder')),
- backgroundColor: Colors.black,
- body: Builder(
- builder: (context) {
- return Stack(
- children: [
- MobileScanner(
- controller: controller,
- errorBuilder: (context, error, child) {
- return ScannerErrorWidget(error: error);
- },
- fit: BoxFit.contain,
- onDetect: (barcodeCapture) {
- setState(() {
- this.barcodeCapture = barcodeCapture;
- });
- },
- onScannerStarted: (arguments) {
- // Do something with arguments.
- },
- ),
- Align(
- alignment: Alignment.bottomCenter,
- child: Container(
- alignment: Alignment.bottomCenter,
- height: 100,
- color: Colors.black.withOpacity(0.4),
- child: Row(
- mainAxisAlignment: MainAxisAlignment.spaceEvenly,
- children: [
- IconButton(
- color: Colors.white,
- icon: ValueListenableBuilder(
- valueListenable: controller.torchState,
- builder: (context, state, child) {
- switch (state) {
- 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: () => controller.toggleTorch(),
- ),
- IconButton(
- color: Colors.white,
- icon: isStarted
- ? const Icon(Icons.stop)
- : const Icon(Icons.play_arrow),
- iconSize: 32.0,
- onPressed: _startOrStop,
- ),
- Center(
- child: SizedBox(
- width: MediaQuery.of(context).size.width - 200,
- height: 50,
- child: FittedBox(
- child: Text(
- '${barcodeCapture?.barcodes.map((e) => e.rawValue) ?? 'Scan something!'}',
- overflow: TextOverflow.fade,
- style: Theme.of(context)
- .textTheme
- .headlineMedium!
- .copyWith(color: Colors.white),
- ),
- ),
- ),
- ),
- IconButton(
- color: Colors.white,
- 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);
- }
- },
- ),
- iconSize: 32.0,
- onPressed: () => controller.switchCamera(),
- ),
- IconButton(
- color: Colors.white,
- icon: const Icon(Icons.image),
- iconSize: 32.0,
- onPressed: () async {
- final ImagePicker picker = ImagePicker();
- // Pick an image
- final XFile? image = await picker.pickImage(
- source: ImageSource.gallery,
- );
- if (image != null) {
- if (await controller.analyzeImage(image.path)) {
- if (!context.mounted) return;
- ScaffoldMessenger.of(context).showSnackBar(
- const SnackBar(
- content: Text('Barcode found!'),
- backgroundColor: Colors.green,
- ),
- );
- } else {
- if (!context.mounted) return;
- ScaffoldMessenger.of(context).showSnackBar(
- const SnackBar(
- content: Text('No barcode found!'),
- backgroundColor: Colors.red,
- ),
- );
- }
- }
- },
- ),
- ],
- ),
- ),
- ),
- ],
- );
- },
- ),
- );
- }
-
- @override
- void dispose() {
- controller.dispose();
- super.dispose();
- }
-}
diff --git a/example/lib/barcode_scanner_controller.dart b/example/lib/barcode_scanner_controller.dart
index 8aac24bbf..126297bd0 100644
--- a/example/lib/barcode_scanner_controller.dart
+++ b/example/lib/barcode_scanner_controller.dart
@@ -1,6 +1,8 @@
+import 'dart:async';
+
import 'package:flutter/material.dart';
-import 'package:image_picker/image_picker.dart';
import 'package:mobile_scanner/mobile_scanner.dart';
+import 'package:mobile_scanner_example/scanner_button_widgets.dart';
import 'package:mobile_scanner_example/scanner_error_widget.dart';
class BarcodeScannerWithController extends StatefulWidget {
@@ -12,10 +14,7 @@ class BarcodeScannerWithController extends StatefulWidget {
}
class _BarcodeScannerWithControllerState
- extends State
- with SingleTickerProviderStateMixin {
- BarcodeCapture? barcode;
-
+ extends State with WidgetsBindingObserver {
final MobileScannerController controller = MobileScannerController(
torchEnabled: true, useNewCameraSelector: true,
// formats: [BarcodeFormat.qrCode]
@@ -25,178 +24,106 @@ class _BarcodeScannerWithControllerState
// returnImage: false,
);
- bool isStarted = true;
+ Barcode? _barcode;
+ StreamSubscription? _subscription;
+
+ Widget _buildBarcode(Barcode? value) {
+ if (value == null) {
+ return const Text(
+ 'Scan something!',
+ overflow: TextOverflow.fade,
+ style: TextStyle(color: Colors.white),
+ );
+ }
+
+ return Text(
+ value.displayValue ?? 'No display value.',
+ overflow: TextOverflow.fade,
+ style: const TextStyle(color: Colors.white),
+ );
+ }
- void _startOrStop() {
- try {
- if (isStarted) {
- controller.stop();
- } else {
- controller.start();
- }
+ void _handleBarcode(BarcodeCapture barcodes) {
+ if (mounted) {
setState(() {
- isStarted = !isStarted;
+ _barcode = barcodes.barcodes.firstOrNull;
});
- } on Exception catch (e) {
- ScaffoldMessenger.of(context).showSnackBar(
- SnackBar(
- content: Text('Something went wrong! $e'),
- backgroundColor: Colors.red,
- ),
- );
}
}
- int? numberOfCameras;
+ @override
+ void initState() {
+ super.initState();
+ WidgetsBinding.instance.addObserver(this);
+
+ _subscription = controller.barcodes.listen(_handleBarcode);
+
+ unawaited(controller.start());
+ }
+
+ @override
+ void didChangeAppLifecycleState(AppLifecycleState state) {
+ super.didChangeAppLifecycleState(state);
+
+ switch (state) {
+ case AppLifecycleState.detached:
+ case AppLifecycleState.hidden:
+ case AppLifecycleState.paused:
+ return;
+ case AppLifecycleState.resumed:
+ _subscription = controller.barcodes.listen(_handleBarcode);
+
+ unawaited(controller.start());
+ case AppLifecycleState.inactive:
+ unawaited(_subscription?.cancel());
+ _subscription = null;
+ unawaited(controller.stop());
+ }
+ }
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('With controller')),
backgroundColor: Colors.black,
- body: Builder(
- builder: (context) {
- return Stack(
- children: [
- MobileScanner(
- onScannerStarted: (arguments) {
- if (mounted && arguments?.numberOfCameras != null) {
- numberOfCameras = arguments!.numberOfCameras;
- setState(() {});
- }
- },
- controller: controller,
- errorBuilder: (context, error, child) {
- return ScannerErrorWidget(error: error);
- },
- fit: BoxFit.contain,
- onDetect: (barcode) {
- setState(() {
- this.barcode = barcode;
- });
- },
- ),
- Align(
- alignment: Alignment.bottomCenter,
- child: Container(
- alignment: Alignment.bottomCenter,
- height: 100,
- color: Colors.black.withOpacity(0.4),
- child: Row(
- mainAxisAlignment: MainAxisAlignment.spaceEvenly,
- children: [
- ValueListenableBuilder(
- valueListenable: controller.hasTorchState,
- builder: (context, state, child) {
- if (state != true) {
- return const SizedBox.shrink();
- }
- return IconButton(
- color: Colors.white,
- icon: ValueListenableBuilder(
- valueListenable: controller.torchState,
- builder: (context, state, child) {
- switch (state) {
- 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: () => controller.toggleTorch(),
- );
- },
- ),
- IconButton(
- color: Colors.white,
- icon: isStarted
- ? const Icon(Icons.stop)
- : const Icon(Icons.play_arrow),
- iconSize: 32.0,
- onPressed: _startOrStop,
- ),
- Center(
- child: SizedBox(
- width: MediaQuery.of(context).size.width - 200,
- height: 50,
- child: FittedBox(
- child: Text(
- barcode?.barcodes.first.rawValue ??
- 'Scan something!',
- overflow: TextOverflow.fade,
- style: Theme.of(context)
- .textTheme
- .headlineMedium!
- .copyWith(color: Colors.white),
- ),
- ),
- ),
- ),
- IconButton(
- color: Colors.white,
- 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);
- }
- },
- ),
- iconSize: 32.0,
- onPressed: (numberOfCameras ?? 0) < 2
- ? null
- : () => controller.switchCamera(),
- ),
- IconButton(
- color: Colors.white,
- icon: const Icon(Icons.image),
- iconSize: 32.0,
- onPressed: () async {
- final ImagePicker picker = ImagePicker();
- // Pick an image
- final XFile? image = await picker.pickImage(
- source: ImageSource.gallery,
- );
- if (image != null) {
- if (await controller.analyzeImage(image.path)) {
- if (!context.mounted) return;
- ScaffoldMessenger.of(context).showSnackBar(
- const SnackBar(
- content: Text('Barcode found!'),
- backgroundColor: Colors.green,
- ),
- );
- } else {
- if (!context.mounted) return;
- ScaffoldMessenger.of(context).showSnackBar(
- const SnackBar(
- content: Text('No barcode found!'),
- backgroundColor: Colors.red,
- ),
- );
- }
- }
- },
- ),
- ],
- ),
- ),
+ body: Stack(
+ children: [
+ MobileScanner(
+ controller: controller,
+ errorBuilder: (context, error, child) {
+ return ScannerErrorWidget(error: error);
+ },
+ fit: BoxFit.contain,
+ ),
+ Align(
+ alignment: Alignment.bottomCenter,
+ child: Container(
+ alignment: Alignment.bottomCenter,
+ height: 100,
+ color: Colors.black.withOpacity(0.4),
+ child: Row(
+ mainAxisAlignment: MainAxisAlignment.spaceEvenly,
+ children: [
+ ToggleFlashlightButton(controller: controller),
+ StartStopMobileScannerButton(controller: controller),
+ Expanded(child: Center(child: _buildBarcode(_barcode))),
+ SwitchCameraButton(controller: controller),
+ AnalyzeImageFromGalleryButton(controller: controller),
+ ],
),
- ],
- );
- },
+ ),
+ ),
+ ],
),
);
}
+
+ @override
+ Future dispose() async {
+ WidgetsBinding.instance.removeObserver(this);
+ unawaited(_subscription?.cancel());
+ _subscription = null;
+ super.dispose();
+ await controller.dispose();
+ }
}
diff --git a/example/lib/barcode_scanner_listview.dart b/example/lib/barcode_scanner_listview.dart
new file mode 100644
index 000000000..ca0de42ba
--- /dev/null
+++ b/example/lib/barcode_scanner_listview.dart
@@ -0,0 +1,113 @@
+import 'dart:async';
+
+import 'package:flutter/material.dart';
+import 'package:mobile_scanner/mobile_scanner.dart';
+import 'package:mobile_scanner_example/scanner_button_widgets.dart';
+import 'package:mobile_scanner_example/scanner_error_widget.dart';
+
+class BarcodeScannerListView extends StatefulWidget {
+ const BarcodeScannerListView({super.key});
+
+ @override
+ State createState() => _BarcodeScannerListViewState();
+}
+
+class _BarcodeScannerListViewState extends State {
+ final MobileScannerController controller = MobileScannerController(
+ torchEnabled: true,
+ // formats: [BarcodeFormat.qrCode]
+ // facing: CameraFacing.front,
+ // detectionSpeed: DetectionSpeed.normal
+ // detectionTimeoutMs: 1000,
+ // returnImage: false,
+ );
+
+ @override
+ void initState() {
+ super.initState();
+
+ controller.start();
+ }
+
+ Widget _buildBarcodesListView() {
+ return StreamBuilder(
+ stream: controller.barcodes,
+ builder: (context, snapshot) {
+ final barcodes = snapshot.data?.barcodes;
+
+ if (barcodes == null || barcodes.isEmpty) {
+ return const Center(
+ child: Text(
+ 'Scan Something!',
+ style: TextStyle(color: Colors.white, fontSize: 20),
+ ),
+ );
+ }
+
+ return ListView.builder(
+ itemCount: barcodes.length,
+ itemBuilder: (context, index) {
+ return Padding(
+ padding: const EdgeInsets.all(8.0),
+ child: Text(
+ barcodes[index].rawValue ?? 'No raw value',
+ overflow: TextOverflow.fade,
+ style: const TextStyle(color: Colors.white),
+ ),
+ );
+ },
+ );
+ },
+ );
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ return Scaffold(
+ appBar: AppBar(title: const Text('With ListView')),
+ backgroundColor: Colors.black,
+ body: Stack(
+ children: [
+ MobileScanner(
+ controller: controller,
+ errorBuilder: (context, error, child) {
+ return ScannerErrorWidget(error: error);
+ },
+ fit: BoxFit.contain,
+ ),
+ Align(
+ alignment: Alignment.bottomCenter,
+ child: Container(
+ alignment: Alignment.bottomCenter,
+ height: 100,
+ color: Colors.black.withOpacity(0.4),
+ child: Column(
+ children: [
+ Expanded(
+ child: _buildBarcodesListView(),
+ ),
+ Row(
+ mainAxisAlignment: MainAxisAlignment.spaceEvenly,
+ children: [
+ ToggleFlashlightButton(controller: controller),
+ StartStopMobileScannerButton(controller: controller),
+ const Spacer(),
+ SwitchCameraButton(controller: controller),
+ AnalyzeImageFromGalleryButton(controller: controller),
+ ],
+ ),
+ ],
+ ),
+ ),
+ ),
+ ],
+ ),
+ );
+ }
+
+ @override
+ Future dispose() async {
+ super.dispose();
+ await controller.dispose();
+ }
+}
diff --git a/example/lib/barcode_scanner_pageview.dart b/example/lib/barcode_scanner_pageview.dart
index b06cf755e..c81865b15 100644
--- a/example/lib/barcode_scanner_pageview.dart
+++ b/example/lib/barcode_scanner_pageview.dart
@@ -1,5 +1,8 @@
+import 'dart:async';
+
import 'package:flutter/material.dart';
import 'package:mobile_scanner/mobile_scanner.dart';
+import 'package:mobile_scanner_example/scanned_barcode_label.dart';
import 'package:mobile_scanner_example/scanner_error_widget.dart';
class BarcodeScannerPageView extends StatefulWidget {
@@ -9,62 +12,15 @@ class BarcodeScannerPageView extends StatefulWidget {
State createState() => _BarcodeScannerPageViewState();
}
-class _BarcodeScannerPageViewState extends State
- with SingleTickerProviderStateMixin {
- BarcodeCapture? capture;
+class _BarcodeScannerPageViewState extends State {
+ final MobileScannerController controller = MobileScannerController();
- Widget cameraView() {
- return Builder(
- builder: (context) {
- return Stack(
- children: [
- MobileScanner(
- startDelay: true,
- controller: MobileScannerController(torchEnabled: true),
- fit: BoxFit.contain,
- errorBuilder: (context, error, child) {
- return ScannerErrorWidget(error: error);
- },
- onDetect: (capture) {
- setState(() {
- this.capture = capture;
- });
- },
- ),
- Align(
- alignment: Alignment.bottomCenter,
- child: Container(
- alignment: Alignment.bottomCenter,
- height: 100,
- color: Colors.black.withOpacity(0.4),
- child: Row(
- mainAxisAlignment: MainAxisAlignment.spaceEvenly,
- children: [
- Center(
- child: SizedBox(
- width: MediaQuery.of(context).size.width - 120,
- height: 50,
- child: FittedBox(
- child: Text(
- capture?.barcodes.first.rawValue ??
- 'Scan something!',
- overflow: TextOverflow.fade,
- style: Theme.of(context)
- .textTheme
- .headlineMedium!
- .copyWith(color: Colors.white),
- ),
- ),
- ),
- ),
- ],
- ),
- ),
- ),
- ],
- );
- },
- );
+ final PageController pageController = PageController();
+
+ @override
+ void initState() {
+ super.initState();
+ unawaited(controller.start());
}
@override
@@ -73,13 +29,68 @@ class _BarcodeScannerPageViewState extends State
appBar: AppBar(title: const Text('With PageView')),
backgroundColor: Colors.black,
body: PageView(
+ controller: pageController,
+ onPageChanged: (index) async {
+ // Stop the camera view for the current page,
+ // and then restart the camera for the new page.
+ await controller.stop();
+
+ // When switching pages, add a delay to the next start call.
+ // Otherwise the camera will start before the next page is displayed.
+ await Future.delayed(const Duration(seconds: 1, milliseconds: 500));
+
+ if (!mounted) {
+ return;
+ }
+
+ unawaited(controller.start());
+ },
children: [
- cameraView(),
- Container(),
- cameraView(),
- cameraView(),
+ _BarcodeScannerPage(controller: controller),
+ const SizedBox(),
+ _BarcodeScannerPage(controller: controller),
+ _BarcodeScannerPage(controller: controller),
],
),
);
}
+
+ @override
+ Future dispose() async {
+ pageController.dispose();
+ super.dispose();
+ await controller.dispose();
+ }
+}
+
+class _BarcodeScannerPage extends StatelessWidget {
+ const _BarcodeScannerPage({required this.controller});
+
+ final MobileScannerController controller;
+
+ @override
+ Widget build(BuildContext context) {
+ return Stack(
+ children: [
+ MobileScanner(
+ controller: controller,
+ fit: BoxFit.contain,
+ errorBuilder: (context, error, child) {
+ return ScannerErrorWidget(error: error);
+ },
+ ),
+ Align(
+ alignment: Alignment.bottomCenter,
+ child: Container(
+ alignment: Alignment.bottomCenter,
+ height: 100,
+ color: Colors.black.withOpacity(0.4),
+ child: Center(
+ child: ScannedBarcodeLabel(barcodes: controller.barcodes),
+ ),
+ ),
+ ),
+ ],
+ );
+ }
}
diff --git a/example/lib/barcode_scanner_returning_image.dart b/example/lib/barcode_scanner_returning_image.dart
index 15c1912a4..5f2943c0e 100644
--- a/example/lib/barcode_scanner_returning_image.dart
+++ b/example/lib/barcode_scanner_returning_image.dart
@@ -2,6 +2,8 @@ import 'dart:math';
import 'package:flutter/material.dart';
import 'package:mobile_scanner/mobile_scanner.dart';
+import 'package:mobile_scanner_example/scanned_barcode_label.dart';
+import 'package:mobile_scanner_example/scanner_button_widgets.dart';
import 'package:mobile_scanner_example/scanner_error_widget.dart';
class BarcodeScannerReturningImage extends StatefulWidget {
@@ -13,11 +15,7 @@ class BarcodeScannerReturningImage extends StatefulWidget {
}
class _BarcodeScannerReturningImageState
- extends State
- with SingleTickerProviderStateMixin {
- BarcodeCapture? barcode;
- // MobileScannerArguments? arguments;
-
+ extends State {
final MobileScannerController controller = MobileScannerController(
torchEnabled: true,
// formats: [BarcodeFormat.qrCode]
@@ -27,26 +25,10 @@ class _BarcodeScannerReturningImageState
returnImage: true,
);
- bool isStarted = true;
-
- void _startOrStop() {
- try {
- if (isStarted) {
- controller.stop();
- } else {
- controller.start();
- }
- setState(() {
- isStarted = !isStarted;
- });
- } on Exception catch (e) {
- ScaffoldMessenger.of(context).showSnackBar(
- SnackBar(
- content: Text('Something went wrong! $e'),
- backgroundColor: Colors.red,
- ),
- );
- }
+ @override
+ void initState() {
+ super.initState();
+ controller.start();
}
@override
@@ -57,20 +39,55 @@ class _BarcodeScannerReturningImageState
child: Column(
children: [
Expanded(
- child: barcode?.image != null
- ? Transform.rotate(
- angle: 90 * pi / 180,
- child: Image(
- gaplessPlayback: true,
- image: MemoryImage(barcode!.image!),
- fit: BoxFit.contain,
- ),
- )
- : const Center(
+ child: StreamBuilder(
+ stream: controller.barcodes,
+ builder: (context, snapshot) {
+ final barcode = snapshot.data;
+
+ if (barcode == null) {
+ return const Center(
child: Text(
'Your scanned barcode will appear here!',
),
- ),
+ );
+ }
+
+ final barcodeImage = barcode.image;
+
+ if (barcodeImage == null) {
+ return const Center(
+ child: Text('No image for this barcode.'),
+ );
+ }
+
+ return Image.memory(
+ barcodeImage,
+ fit: BoxFit.contain,
+ errorBuilder: (context, error, stackTrace) {
+ return Center(
+ child: Text('Could not decode image bytes. $error'),
+ );
+ },
+ frameBuilder: (
+ BuildContext context,
+ Widget child,
+ int? frame,
+ bool? wasSynchronouslyLoaded,
+ ) {
+ if (wasSynchronouslyLoaded == true || frame != null) {
+ return Transform.rotate(
+ angle: 90 * pi / 180,
+ child: child,
+ );
+ }
+
+ return const Center(
+ child: CircularProgressIndicator(),
+ );
+ },
+ );
+ },
+ ),
),
Expanded(
flex: 2,
@@ -84,11 +101,6 @@ class _BarcodeScannerReturningImageState
return ScannerErrorWidget(error: error);
},
fit: BoxFit.contain,
- onDetect: (barcode) {
- setState(() {
- this.barcode = barcode;
- });
- },
),
Align(
alignment: Alignment.bottomCenter,
@@ -99,69 +111,18 @@ class _BarcodeScannerReturningImageState
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
- IconButton(
- color: Colors.white,
- icon: ValueListenableBuilder(
- valueListenable: controller.torchState,
- builder: (context, state, child) {
- switch (state) {
- 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: () => controller.toggleTorch(),
- ),
- IconButton(
- color: Colors.white,
- icon: isStarted
- ? const Icon(Icons.stop)
- : const Icon(Icons.play_arrow),
- iconSize: 32.0,
- onPressed: _startOrStop,
+ ToggleFlashlightButton(controller: controller),
+ StartStopMobileScannerButton(
+ controller: controller,
),
- Center(
- child: SizedBox(
- width: MediaQuery.of(context).size.width - 200,
- height: 50,
- child: FittedBox(
- child: Text(
- barcode?.barcodes.first.rawValue ??
- 'Scan something!',
- overflow: TextOverflow.fade,
- style: Theme.of(context)
- .textTheme
- .headlineMedium!
- .copyWith(color: Colors.white),
- ),
+ Expanded(
+ child: Center(
+ child: ScannedBarcodeLabel(
+ barcodes: controller.barcodes,
),
),
),
- IconButton(
- color: Colors.white,
- 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);
- }
- },
- ),
- iconSize: 32.0,
- onPressed: () => controller.switchCamera(),
- ),
+ SwitchCameraButton(controller: controller),
],
),
),
@@ -177,8 +138,8 @@ class _BarcodeScannerReturningImageState
}
@override
- void dispose() {
- controller.dispose();
+ Future dispose() async {
super.dispose();
+ await controller.dispose();
}
}
diff --git a/example/lib/barcode_scanner_window.dart b/example/lib/barcode_scanner_window.dart
index 974ff5053..dd52f618e 100644
--- a/example/lib/barcode_scanner_window.dart
+++ b/example/lib/barcode_scanner_window.dart
@@ -3,6 +3,7 @@ import 'dart:io';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:mobile_scanner/mobile_scanner.dart';
+import 'package:mobile_scanner_example/scanned_barcode_label.dart';
import 'package:mobile_scanner_example/scanner_error_widget.dart';
@@ -16,95 +17,120 @@ class BarcodeScannerWithScanWindow extends StatefulWidget {
class _BarcodeScannerWithScanWindowState
extends State {
- late MobileScannerController controller = MobileScannerController();
- Barcode? barcode;
- BarcodeCapture? capture;
+ final MobileScannerController controller = MobileScannerController();
- Future onDetect(BarcodeCapture barcode) async {
- capture = barcode;
- setState(() => this.barcode = barcode.barcodes.first);
+ @override
+ void initState() {
+ super.initState();
+
+ controller.start();
}
- MobileScannerArguments? arguments;
+ Widget _buildBarcodeOverlay() {
+ return ValueListenableBuilder(
+ valueListenable: controller,
+ builder: (context, value, child) {
+ // Not ready.
+ if (!value.isInitialized || !value.isRunning || value.error != null) {
+ return const SizedBox();
+ }
+
+ return StreamBuilder(
+ stream: controller.barcodes,
+ builder: (context, snapshot) {
+ final BarcodeCapture? barcodeCapture = snapshot.data;
+
+ // No barcode.
+ if (barcodeCapture == null || barcodeCapture.barcodes.isEmpty) {
+ return const SizedBox();
+ }
+
+ final scannedBarcode = barcodeCapture.barcodes.first;
+
+ // No barcode corners, or size, or no camera preview size.
+ if (scannedBarcode.corners.isEmpty ||
+ value.size.isEmpty ||
+ barcodeCapture.size.isEmpty) {
+ return const SizedBox();
+ }
+
+ return CustomPaint(
+ painter: BarcodeOverlay(
+ barcodeCorners: scannedBarcode.corners,
+ barcodeSize: barcodeCapture.size,
+ boxFit: BoxFit.contain,
+ cameraPreviewSize: value.size,
+ ),
+ );
+ },
+ );
+ },
+ );
+ }
+
+ Widget _buildScanWindow(Rect scanWindowRect) {
+ return ValueListenableBuilder(
+ valueListenable: controller,
+ builder: (context, value, child) {
+ // Not ready.
+ if (!value.isInitialized ||
+ !value.isRunning ||
+ value.error != null ||
+ value.size.isEmpty) {
+ return const SizedBox();
+ }
+
+ return CustomPaint(
+ painter: ScannerOverlay(scanWindowRect),
+ );
+ },
+ );
+ }
@override
Widget build(BuildContext context) {
final scanWindow = Rect.fromCenter(
- center: MediaQuery.of(context).size.center(Offset.zero),
+ center: MediaQuery.sizeOf(context).center(Offset.zero),
width: 200,
height: 200,
);
+
return Scaffold(
appBar: AppBar(title: const Text('With Scan window')),
backgroundColor: Colors.black,
- body: Builder(
- builder: (context) {
- return Stack(
- fit: StackFit.expand,
- children: [
- MobileScanner(
- fit: BoxFit.contain,
- scanWindow: scanWindow,
- controller: controller,
- onScannerStarted: (arguments) {
- setState(() {
- this.arguments = arguments;
- });
- },
- errorBuilder: (context, error, child) {
- return ScannerErrorWidget(error: error);
- },
- onDetect: onDetect,
- ),
- if (barcode != null &&
- barcode?.corners != null &&
- arguments != null)
- CustomPaint(
- painter: BarcodeOverlay(
- barcode: barcode!,
- arguments: arguments!,
- boxFit: BoxFit.contain,
- capture: capture!,
- ),
- ),
- CustomPaint(
- painter: ScannerOverlay(scanWindow),
- ),
- Align(
- alignment: Alignment.bottomCenter,
- child: Container(
- alignment: Alignment.bottomCenter,
- height: 100,
- color: Colors.black.withOpacity(0.4),
- child: Row(
- mainAxisAlignment: MainAxisAlignment.spaceEvenly,
- children: [
- Center(
- child: SizedBox(
- width: MediaQuery.of(context).size.width - 120,
- height: 50,
- child: FittedBox(
- child: Text(
- barcode?.displayValue ?? 'Scan something!',
- overflow: TextOverflow.fade,
- style: Theme.of(context)
- .textTheme
- .headlineMedium!
- .copyWith(color: Colors.white),
- ),
- ),
- ),
- ),
- ],
- ),
- ),
- ),
- ],
- );
- },
+ body: Stack(
+ fit: StackFit.expand,
+ children: [
+ MobileScanner(
+ fit: BoxFit.contain,
+ scanWindow: scanWindow,
+ controller: controller,
+ errorBuilder: (context, error, child) {
+ return ScannerErrorWidget(error: error);
+ },
+ ),
+ _buildBarcodeOverlay(),
+ _buildScanWindow(scanWindow),
+ Align(
+ alignment: Alignment.bottomCenter,
+ child: Container(
+ alignment: Alignment.center,
+ padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
+ height: 100,
+ color: Colors.black.withOpacity(0.4),
+ child: ScannedBarcodeLabel(barcodes: controller.barcodes),
+ ),
+ ),
+ ],
),
);
}
+
+ @override
+ Future dispose() async {
+ super.dispose();
+ await controller.dispose();
+ }
}
class ScannerOverlay extends CustomPainter {
@@ -114,6 +140,8 @@ class ScannerOverlay extends CustomPainter {
@override
void paint(Canvas canvas, Size size) {
+ // TODO: use `Offset.zero & size` instead of Rect.largest
+ // we need to pass the size to the custom paint widget
final backgroundPath = Path()..addRect(Rect.largest);
final cutoutPath = Path()..addRect(scanWindow);
@@ -138,24 +166,26 @@ class ScannerOverlay extends CustomPainter {
class BarcodeOverlay extends CustomPainter {
BarcodeOverlay({
- required this.barcode,
- required this.arguments,
+ required this.barcodeCorners,
+ required this.barcodeSize,
required this.boxFit,
- required this.capture,
+ required this.cameraPreviewSize,
});
- final BarcodeCapture capture;
- final Barcode barcode;
- final MobileScannerArguments arguments;
+ final List barcodeCorners;
+ final Size barcodeSize;
final BoxFit boxFit;
+ final Size cameraPreviewSize;
@override
void paint(Canvas canvas, Size size) {
- if (barcode.corners.isEmpty) {
+ if (barcodeCorners.isEmpty ||
+ barcodeSize.isEmpty ||
+ cameraPreviewSize.isEmpty) {
return;
}
- final adjustedSize = applyBoxFit(boxFit, arguments.size, size);
+ final adjustedSize = applyBoxFit(boxFit, cameraPreviewSize, size);
double verticalPadding = size.height - adjustedSize.destination.height;
double horizontalPadding = size.width - adjustedSize.destination.width;
@@ -175,22 +205,21 @@ class BarcodeOverlay extends CustomPainter {
final double ratioHeight;
if (!kIsWeb && Platform.isIOS) {
- ratioWidth = capture.size.width / adjustedSize.destination.width;
- ratioHeight = capture.size.height / adjustedSize.destination.height;
+ ratioWidth = barcodeSize.width / adjustedSize.destination.width;
+ ratioHeight = barcodeSize.height / adjustedSize.destination.height;
} else {
- ratioWidth = arguments.size.width / adjustedSize.destination.width;
- ratioHeight = arguments.size.height / adjustedSize.destination.height;
+ ratioWidth = cameraPreviewSize.width / adjustedSize.destination.width;
+ ratioHeight = cameraPreviewSize.height / adjustedSize.destination.height;
}
- final List adjustedOffset = [];
- for (final offset in barcode.corners) {
- adjustedOffset.add(
+ final List adjustedOffset = [
+ for (final offset in barcodeCorners)
Offset(
offset.dx / ratioWidth + horizontalPadding,
offset.dy / ratioHeight + verticalPadding,
),
- );
- }
+ ];
+
final cutoutPath = Path()..addPolygon(adjustedOffset, true);
final backgroundPaint = Paint()
diff --git a/example/lib/barcode_scanner_without_controller.dart b/example/lib/barcode_scanner_without_controller.dart
deleted file mode 100644
index fe4c84252..000000000
--- a/example/lib/barcode_scanner_without_controller.dart
+++ /dev/null
@@ -1,74 +0,0 @@
-import 'package:flutter/material.dart';
-import 'package:mobile_scanner/mobile_scanner.dart';
-import 'package:mobile_scanner_example/scanner_error_widget.dart';
-
-class BarcodeScannerWithoutController extends StatefulWidget {
- const BarcodeScannerWithoutController({super.key});
-
- @override
- State createState() =>
- _BarcodeScannerWithoutControllerState();
-}
-
-class _BarcodeScannerWithoutControllerState
- extends State
- with SingleTickerProviderStateMixin {
- BarcodeCapture? capture;
-
- @override
- Widget build(BuildContext context) {
- return Scaffold(
- appBar: AppBar(title: const Text('Without controller')),
- backgroundColor: Colors.black,
- body: Builder(
- builder: (context) {
- return Stack(
- children: [
- MobileScanner(
- fit: BoxFit.contain,
- errorBuilder: (context, error, child) {
- return ScannerErrorWidget(error: error);
- },
- onDetect: (capture) {
- setState(() {
- this.capture = capture;
- });
- },
- ),
- Align(
- alignment: Alignment.bottomCenter,
- child: Container(
- alignment: Alignment.bottomCenter,
- height: 100,
- color: Colors.black.withOpacity(0.4),
- child: Row(
- mainAxisAlignment: MainAxisAlignment.spaceEvenly,
- children: [
- Center(
- child: SizedBox(
- width: MediaQuery.of(context).size.width - 120,
- height: 50,
- child: FittedBox(
- child: Text(
- capture?.barcodes.first.rawValue ??
- 'Scan something!',
- overflow: TextOverflow.fade,
- style: Theme.of(context)
- .textTheme
- .headlineMedium!
- .copyWith(color: Colors.white),
- ),
- ),
- ),
- ),
- ],
- ),
- ),
- ),
- ],
- );
- },
- ),
- );
- }
-}
diff --git a/example/lib/barcode_scanner_zoom.dart b/example/lib/barcode_scanner_zoom.dart
index 665968b2d..4cd36ecb6 100644
--- a/example/lib/barcode_scanner_zoom.dart
+++ b/example/lib/barcode_scanner_zoom.dart
@@ -1,7 +1,11 @@
+import 'dart:async';
+
+import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
-import 'package:image_picker/image_picker.dart';
import 'package:mobile_scanner/mobile_scanner.dart';
+import 'package:mobile_scanner_example/scanned_barcode_label.dart';
+import 'package:mobile_scanner_example/scanner_button_widgets.dart';
import 'package:mobile_scanner_example/scanner_error_widget.dart';
class BarcodeScannerWithZoom extends StatefulWidget {
@@ -11,195 +15,115 @@ class BarcodeScannerWithZoom extends StatefulWidget {
State createState() => _BarcodeScannerWithZoomState();
}
-class _BarcodeScannerWithZoomState extends State
- with SingleTickerProviderStateMixin {
- BarcodeCapture? barcode;
-
- MobileScannerController controller = MobileScannerController(
+class _BarcodeScannerWithZoomState extends State {
+ final MobileScannerController controller = MobileScannerController(
torchEnabled: true,
);
- bool isStarted = true;
double _zoomFactor = 0.0;
+ @override
+ void initState() {
+ super.initState();
+ controller.start();
+ }
+
+ Widget _buildZoomScaleSlider() {
+ return ValueListenableBuilder(
+ valueListenable: controller,
+ builder: (context, state, child) {
+ if (!state.isInitialized || !state.isRunning) {
+ return const SizedBox.shrink();
+ }
+
+ final TextStyle labelStyle = Theme.of(context)
+ .textTheme
+ .headlineMedium!
+ .copyWith(color: Colors.white);
+
+ return Padding(
+ padding: const EdgeInsets.symmetric(horizontal: 8.0),
+ child: Row(
+ children: [
+ Text(
+ '0%',
+ overflow: TextOverflow.fade,
+ style: labelStyle,
+ ),
+ Expanded(
+ child: Slider(
+ value: _zoomFactor,
+ onChanged: (value) {
+ setState(() {
+ _zoomFactor = value;
+ controller.setZoomScale(value);
+ });
+ },
+ ),
+ ),
+ Text(
+ '100%',
+ overflow: TextOverflow.fade,
+ style: labelStyle,
+ ),
+ ],
+ ),
+ );
+ },
+ );
+ }
+
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('With zoom slider')),
backgroundColor: Colors.black,
- body: Builder(
- builder: (context) {
- return Stack(
- children: [
- MobileScanner(
- controller: controller,
- fit: BoxFit.contain,
- errorBuilder: (context, error, child) {
- return ScannerErrorWidget(error: error);
- },
- onDetect: (barcode) {
- setState(() {
- this.barcode = barcode;
- });
- },
- ),
- Align(
- alignment: Alignment.bottomCenter,
- child: Container(
- alignment: Alignment.bottomCenter,
- height: 100,
- color: Colors.black.withOpacity(0.4),
- child: Column(
+ body: Stack(
+ children: [
+ MobileScanner(
+ controller: controller,
+ fit: BoxFit.contain,
+ errorBuilder: (context, error, child) {
+ return ScannerErrorWidget(error: error);
+ },
+ ),
+ Align(
+ alignment: Alignment.bottomCenter,
+ child: Container(
+ alignment: Alignment.bottomCenter,
+ height: 100,
+ color: Colors.black.withOpacity(0.4),
+ child: Column(
+ children: [
+ if (!kIsWeb) _buildZoomScaleSlider(),
+ Row(
+ mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
- Padding(
- padding: const EdgeInsets.symmetric(horizontal: 8.0),
- child: Row(
- children: [
- Text(
- "0%",
- overflow: TextOverflow.fade,
- style: Theme.of(context)
- .textTheme
- .headlineMedium!
- .copyWith(color: Colors.white),
- ),
- Expanded(
- child: Slider(
- max: 100,
- divisions: 100,
- value: _zoomFactor,
- label: "${_zoomFactor.round()} %",
- onChanged: (value) {
- setState(() {
- _zoomFactor = value;
- controller.setZoomScale(value);
- });
- },
- ),
- ),
- Text(
- "100%",
- overflow: TextOverflow.fade,
- style: Theme.of(context)
- .textTheme
- .headlineMedium!
- .copyWith(color: Colors.white),
- ),
- ],
- ),
- ),
- Row(
- mainAxisAlignment: MainAxisAlignment.spaceEvenly,
- children: [
- IconButton(
- color: Colors.white,
- icon: ValueListenableBuilder(
- valueListenable: controller.torchState,
- builder: (context, state, child) {
- switch (state) {
- 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: () => controller.toggleTorch(),
- ),
- IconButton(
- color: Colors.white,
- icon: isStarted
- ? const Icon(Icons.stop)
- : const Icon(Icons.play_arrow),
- iconSize: 32.0,
- onPressed: () => setState(() {
- isStarted
- ? controller.stop()
- : controller.start();
- isStarted = !isStarted;
- }),
- ),
- Center(
- child: SizedBox(
- width: MediaQuery.of(context).size.width - 200,
- height: 50,
- child: FittedBox(
- child: Text(
- barcode?.barcodes.first.rawValue ??
- 'Scan something!',
- overflow: TextOverflow.fade,
- style: Theme.of(context)
- .textTheme
- .headlineMedium!
- .copyWith(color: Colors.white),
- ),
- ),
- ),
- ),
- IconButton(
- color: Colors.white,
- 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);
- }
- },
- ),
- iconSize: 32.0,
- onPressed: () => controller.switchCamera(),
+ ToggleFlashlightButton(controller: controller),
+ StartStopMobileScannerButton(controller: controller),
+ Expanded(
+ child: Center(
+ child: ScannedBarcodeLabel(
+ barcodes: controller.barcodes,
),
- IconButton(
- color: Colors.white,
- icon: const Icon(Icons.image),
- iconSize: 32.0,
- onPressed: () async {
- final ImagePicker picker = ImagePicker();
- // Pick an image
- final XFile? image = await picker.pickImage(
- source: ImageSource.gallery,
- );
- if (image != null) {
- if (await controller.analyzeImage(image.path)) {
- if (!context.mounted) return;
- ScaffoldMessenger.of(context).showSnackBar(
- const SnackBar(
- content: Text('Barcode found!'),
- backgroundColor: Colors.green,
- ),
- );
- } else {
- if (!context.mounted) return;
- ScaffoldMessenger.of(context).showSnackBar(
- const SnackBar(
- content: Text('No barcode found!'),
- backgroundColor: Colors.red,
- ),
- );
- }
- }
- },
- ),
- ],
+ ),
),
+ SwitchCameraButton(controller: controller),
+ AnalyzeImageFromGalleryButton(controller: controller),
],
),
- ),
+ ],
),
- ],
- );
- },
+ ),
+ ),
+ ],
),
);
}
+
+ @override
+ Future dispose() async {
+ super.dispose();
+ await controller.dispose();
+ }
}
diff --git a/example/lib/main.dart b/example/lib/main.dart
index 5c0fdf6e9..082e3395e 100644
--- a/example/lib/main.dart
+++ b/example/lib/main.dart
@@ -1,14 +1,20 @@
import 'package:flutter/material.dart';
-import 'package:mobile_scanner_example/barcode_list_scanner_controller.dart';
import 'package:mobile_scanner_example/barcode_scanner_controller.dart';
+import 'package:mobile_scanner_example/barcode_scanner_listview.dart';
import 'package:mobile_scanner_example/barcode_scanner_pageview.dart';
import 'package:mobile_scanner_example/barcode_scanner_returning_image.dart';
import 'package:mobile_scanner_example/barcode_scanner_window.dart';
-import 'package:mobile_scanner_example/barcode_scanner_without_controller.dart';
import 'package:mobile_scanner_example/barcode_scanner_zoom.dart';
import 'package:mobile_scanner_example/mobile_scanner_overlay.dart';
-void main() => runApp(const MaterialApp(home: MyHome()));
+void main() {
+ runApp(
+ const MaterialApp(
+ title: 'Mobile Scanner Example',
+ home: MyHome(),
+ ),
+ );
+}
class MyHome extends StatelessWidget {
const MyHome({super.key});
@@ -16,23 +22,20 @@ class MyHome extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
- appBar: AppBar(title: const Text('Flutter Demo Home Page')),
- body: SizedBox(
- width: MediaQuery.of(context).size.width,
- height: MediaQuery.of(context).size.height,
+ appBar: AppBar(title: const Text('Mobile Scanner Example')),
+ body: Center(
child: Column(
- mainAxisAlignment: MainAxisAlignment.center,
+ mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
ElevatedButton(
onPressed: () {
Navigator.of(context).push(
MaterialPageRoute(
- builder: (context) =>
- const BarcodeListScannerWithController(),
+ builder: (context) => const BarcodeScannerListView(),
),
);
},
- child: const Text('MobileScanner with List Controller'),
+ child: const Text('MobileScanner with ListView'),
),
ElevatedButton(
onPressed: () {
@@ -62,19 +65,9 @@ class MyHome extends StatelessWidget {
),
);
},
- child:
- const Text('MobileScanner with Controller (returning image)'),
- ),
- ElevatedButton(
- onPressed: () {
- Navigator.of(context).push(
- MaterialPageRoute(
- builder: (context) =>
- const BarcodeScannerWithoutController(),
- ),
- );
- },
- child: const Text('MobileScanner without Controller'),
+ child: const Text(
+ 'MobileScanner with Controller (returning image)',
+ ),
),
ElevatedButton(
onPressed: () {
diff --git a/example/lib/mobile_scanner_overlay.dart b/example/lib/mobile_scanner_overlay.dart
index cff099b38..922a63409 100644
--- a/example/lib/mobile_scanner_overlay.dart
+++ b/example/lib/mobile_scanner_overlay.dart
@@ -1,5 +1,7 @@
import 'package:flutter/material.dart';
import 'package:mobile_scanner/mobile_scanner.dart';
+import 'package:mobile_scanner_example/scanned_barcode_label.dart';
+import 'package:mobile_scanner_example/scanner_button_widgets.dart';
import 'package:mobile_scanner_example/scanner_error_widget.dart';
class BarcodeScannerWithOverlay extends StatefulWidget {
@@ -9,174 +11,105 @@ class BarcodeScannerWithOverlay extends StatefulWidget {
}
class _BarcodeScannerWithOverlayState extends State {
- String overlayText = "Please scan QR Code";
- bool camStarted = false;
-
final MobileScannerController controller = MobileScannerController(
formats: const [BarcodeFormat.qrCode],
- autoStart: false,
);
@override
- void dispose() {
- controller.dispose();
- super.dispose();
- }
-
- void startCamera() {
- if (camStarted) {
- return;
- }
-
- controller.start().then((_) {
- if (mounted) {
- setState(() {
- camStarted = true;
- });
- }
- }).catchError((Object error, StackTrace stackTrace) {
- if (mounted) {
- ScaffoldMessenger.of(context).showSnackBar(
- SnackBar(
- content: Text('Something went wrong! $error'),
- backgroundColor: Colors.red,
- ),
- );
- }
- });
- }
-
- void onBarcodeDetect(BarcodeCapture barcodeCapture) {
- final barcode = barcodeCapture.barcodes.last;
- setState(() {
- overlayText = barcodeCapture.barcodes.last.displayValue ??
- barcode.rawValue ??
- 'Barcode has no displayable value';
- });
+ void initState() {
+ super.initState();
+ controller.start();
}
@override
Widget build(BuildContext context) {
final scanWindow = Rect.fromCenter(
- center: MediaQuery.of(context).size.center(Offset.zero),
+ center: MediaQuery.sizeOf(context).center(Offset.zero),
width: 200,
height: 200,
);
return Scaffold(
+ backgroundColor: Colors.black,
appBar: AppBar(
title: const Text('Scanner with Overlay Example app'),
),
- body: Center(
- child: Column(
- mainAxisAlignment: MainAxisAlignment.center,
- children: [
- Expanded(
- child: camStarted
- ? Stack(
- fit: StackFit.expand,
- children: [
- Center(
- child: MobileScanner(
- fit: BoxFit.contain,
- onDetect: onBarcodeDetect,
- overlay: Padding(
- padding: const EdgeInsets.all(16.0),
- child: Align(
- alignment: Alignment.bottomCenter,
- child: Opacity(
- opacity: 0.7,
- child: Text(
- overlayText,
- style: const TextStyle(
- backgroundColor: Colors.black26,
- color: Colors.white,
- fontWeight: FontWeight.bold,
- fontSize: 24,
- overflow: TextOverflow.ellipsis,
- ),
- maxLines: 1,
- ),
- ),
- ),
- ),
- controller: controller,
- scanWindow: scanWindow,
- errorBuilder: (context, error, child) {
- return ScannerErrorWidget(error: error);
- },
- ),
- ),
- CustomPaint(
- painter: ScannerOverlay(scanWindow),
- ),
- Padding(
- padding: const EdgeInsets.all(16.0),
- child: Align(
- alignment: Alignment.bottomCenter,
- child: Row(
- mainAxisAlignment: MainAxisAlignment.spaceEvenly,
- children: [
- ValueListenableBuilder(
- valueListenable: controller.torchState,
- builder: (context, value, child) {
- final Color iconColor;
-
- switch (value) {
- case TorchState.off:
- iconColor = Colors.black;
- case TorchState.on:
- iconColor = Colors.yellow;
- }
-
- return IconButton(
- onPressed: () => controller.toggleTorch(),
- icon: Icon(
- Icons.flashlight_on,
- color: iconColor,
- ),
- );
- },
- ),
- IconButton(
- onPressed: () => controller.switchCamera(),
- icon: const Icon(
- Icons.cameraswitch_rounded,
- color: Colors.white,
- ),
- ),
- ],
- ),
- ),
- ),
- ],
- )
- : const Center(
- child: Text("Tap on Camera to activate QR Scanner"),
- ),
+ body: Stack(
+ fit: StackFit.expand,
+ children: [
+ Center(
+ child: MobileScanner(
+ fit: BoxFit.contain,
+ controller: controller,
+ scanWindow: scanWindow,
+ errorBuilder: (context, error, child) {
+ return ScannerErrorWidget(error: error);
+ },
+ overlayBuilder: (context, constraints) {
+ return Padding(
+ padding: const EdgeInsets.all(16.0),
+ child: Align(
+ alignment: Alignment.bottomCenter,
+ child: ScannedBarcodeLabel(barcodes: controller.barcodes),
+ ),
+ );
+ },
),
- ],
- ),
- ),
- floatingActionButton: camStarted
- ? null
- : FloatingActionButton(
- onPressed: startCamera,
- child: const Icon(Icons.camera_alt),
+ ),
+ ValueListenableBuilder(
+ valueListenable: controller,
+ builder: (context, value, child) {
+ if (!value.isInitialized ||
+ !value.isRunning ||
+ value.error != null) {
+ return const SizedBox();
+ }
+
+ return CustomPaint(
+ painter: ScannerOverlay(scanWindow: scanWindow),
+ );
+ },
+ ),
+ Align(
+ alignment: Alignment.bottomCenter,
+ child: Padding(
+ padding: const EdgeInsets.all(16.0),
+ child: Row(
+ mainAxisAlignment: MainAxisAlignment.spaceEvenly,
+ children: [
+ ToggleFlashlightButton(controller: controller),
+ SwitchCameraButton(controller: controller),
+ ],
+ ),
),
+ ),
+ ],
+ ),
);
}
+
+ @override
+ Future dispose() async {
+ super.dispose();
+ await controller.dispose();
+ }
}
class ScannerOverlay extends CustomPainter {
- ScannerOverlay(this.scanWindow);
+ const ScannerOverlay({
+ required this.scanWindow,
+ this.borderRadius = 12.0,
+ });
final Rect scanWindow;
- final double borderRadius = 12.0;
+ final double borderRadius;
@override
void paint(Canvas canvas, Size size) {
+ // TODO: use `Offset.zero & size` instead of Rect.largest
+ // we need to pass the size to the custom paint widget
final backgroundPath = Path()..addRect(Rect.largest);
+
final cutoutPath = Path()
..addRRect(
RRect.fromRectAndCorners(
@@ -199,14 +132,11 @@ class ScannerOverlay extends CustomPainter {
cutoutPath,
);
- // Create a Paint object for the white border
final borderPaint = Paint()
..color = Colors.white
..style = PaintingStyle.stroke
- ..strokeWidth = 4.0; // Adjust the border width as needed
+ ..strokeWidth = 4.0;
- // Calculate the border rectangle with rounded corners
-// Adjust the radius as needed
final borderRect = RRect.fromRectAndCorners(
scanWindow,
topLeft: Radius.circular(borderRadius),
@@ -215,13 +145,16 @@ class ScannerOverlay extends CustomPainter {
bottomRight: Radius.circular(borderRadius),
);
- // Draw the white border
+ // First, draw the background,
+ // with a cutout area that is a bit larger than the scan window.
+ // Finally, draw the scan window itself.
canvas.drawPath(backgroundWithCutout, backgroundPaint);
canvas.drawRRect(borderRect, borderPaint);
}
@override
- bool shouldRepaint(covariant CustomPainter oldDelegate) {
- return false;
+ bool shouldRepaint(ScannerOverlay oldDelegate) {
+ return scanWindow != oldDelegate.scanWindow ||
+ borderRadius != oldDelegate.borderRadius;
}
}
diff --git a/example/lib/scanned_barcode_label.dart b/example/lib/scanned_barcode_label.dart
new file mode 100644
index 000000000..c7f168f6d
--- /dev/null
+++ b/example/lib/scanned_barcode_label.dart
@@ -0,0 +1,35 @@
+import 'package:flutter/material.dart';
+import 'package:mobile_scanner/mobile_scanner.dart';
+
+class ScannedBarcodeLabel extends StatelessWidget {
+ const ScannedBarcodeLabel({
+ super.key,
+ required this.barcodes,
+ });
+
+ final Stream barcodes;
+
+ @override
+ Widget build(BuildContext context) {
+ return StreamBuilder(
+ stream: barcodes,
+ builder: (context, snaphot) {
+ final scannedBarcodes = snaphot.data?.barcodes ?? [];
+
+ if (scannedBarcodes.isEmpty) {
+ return const Text(
+ 'Scan something!',
+ overflow: TextOverflow.fade,
+ style: TextStyle(color: Colors.white),
+ );
+ }
+
+ return Text(
+ scannedBarcodes.first.displayValue ?? 'No display value.',
+ overflow: TextOverflow.fade,
+ style: const TextStyle(color: Colors.white),
+ );
+ },
+ );
+ }
+}
diff --git a/example/lib/scanner_button_widgets.dart b/example/lib/scanner_button_widgets.dart
new file mode 100644
index 000000000..427441cdc
--- /dev/null
+++ b/example/lib/scanner_button_widgets.dart
@@ -0,0 +1,168 @@
+import 'package:flutter/material.dart';
+import 'package:image_picker/image_picker.dart';
+import 'package:mobile_scanner/mobile_scanner.dart';
+
+class AnalyzeImageFromGalleryButton extends StatelessWidget {
+ const AnalyzeImageFromGalleryButton({required this.controller, super.key});
+
+ final MobileScannerController controller;
+
+ @override
+ Widget build(BuildContext context) {
+ return IconButton(
+ color: Colors.white,
+ icon: const Icon(Icons.image),
+ iconSize: 32.0,
+ onPressed: () async {
+ final ImagePicker picker = ImagePicker();
+
+ final XFile? image = await picker.pickImage(
+ source: ImageSource.gallery,
+ );
+
+ if (image == null) {
+ return;
+ }
+
+ final BarcodeCapture? barcodes = await controller.analyzeImage(
+ image.path,
+ );
+
+ if (!context.mounted) {
+ return;
+ }
+
+ final SnackBar snackbar = barcodes != null
+ ? const SnackBar(
+ content: Text('Barcode found!'),
+ backgroundColor: Colors.green,
+ )
+ : const SnackBar(
+ content: Text('No barcode found!'),
+ backgroundColor: Colors.red,
+ );
+
+ ScaffoldMessenger.of(context).showSnackBar(snackbar);
+ },
+ );
+ }
+}
+
+class StartStopMobileScannerButton extends StatelessWidget {
+ const StartStopMobileScannerButton({required this.controller, super.key});
+
+ final MobileScannerController controller;
+
+ @override
+ Widget build(BuildContext context) {
+ return ValueListenableBuilder(
+ valueListenable: controller,
+ builder: (context, state, child) {
+ if (!state.isInitialized || !state.isRunning) {
+ return IconButton(
+ color: Colors.white,
+ icon: const Icon(Icons.play_arrow),
+ iconSize: 32.0,
+ onPressed: () async {
+ await controller.start();
+ },
+ );
+ }
+
+ return IconButton(
+ color: Colors.white,
+ icon: const Icon(Icons.stop),
+ iconSize: 32.0,
+ onPressed: () async {
+ await controller.stop();
+ },
+ );
+ },
+ );
+ }
+}
+
+class SwitchCameraButton extends StatelessWidget {
+ const SwitchCameraButton({required this.controller, super.key});
+
+ final MobileScannerController controller;
+
+ @override
+ Widget build(BuildContext context) {
+ return ValueListenableBuilder(
+ valueListenable: controller,
+ builder: (context, state, child) {
+ if (!state.isInitialized || !state.isRunning) {
+ return const SizedBox.shrink();
+ }
+
+ final int? availableCameras = state.availableCameras;
+
+ if (availableCameras != null && availableCameras < 2) {
+ return const SizedBox.shrink();
+ }
+
+ final Widget icon;
+
+ switch (state.cameraDirection) {
+ case CameraFacing.front:
+ icon = const Icon(Icons.camera_front);
+ case CameraFacing.back:
+ icon = const Icon(Icons.camera_rear);
+ }
+
+ return IconButton(
+ iconSize: 32.0,
+ icon: icon,
+ onPressed: () async {
+ await controller.switchCamera();
+ },
+ );
+ },
+ );
+ }
+}
+
+class ToggleFlashlightButton extends StatelessWidget {
+ const ToggleFlashlightButton({required this.controller, super.key});
+
+ final MobileScannerController controller;
+
+ @override
+ Widget build(BuildContext context) {
+ return ValueListenableBuilder(
+ valueListenable: controller,
+ builder: (context, state, child) {
+ if (!state.isInitialized || !state.isRunning) {
+ return const SizedBox.shrink();
+ }
+
+ switch (state.torchState) {
+ case TorchState.off:
+ return IconButton(
+ color: Colors.white,
+ iconSize: 32.0,
+ icon: const Icon(Icons.flash_off),
+ onPressed: () async {
+ await controller.toggleTorch();
+ },
+ );
+ case TorchState.on:
+ return IconButton(
+ color: Colors.white,
+ iconSize: 32.0,
+ icon: const Icon(Icons.flash_on),
+ onPressed: () async {
+ await controller.toggleTorch();
+ },
+ );
+ case TorchState.unavailable:
+ return const Icon(
+ Icons.no_flash,
+ color: Colors.grey,
+ );
+ }
+ },
+ );
+ }
+}
diff --git a/example/pubspec.yaml b/example/pubspec.yaml
index 1faac68b9..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.1.0 <4.0.0"
- flutter: ">=3.13.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/example/web/index.html b/example/web/index.html
index bc900fb63..5beb6ff8e 100644
--- a/example/web/index.html
+++ b/example/web/index.html
@@ -40,7 +40,6 @@
-