Showing
15 changed files
with
258 additions
and
191 deletions
| 1 | -## NEXT | 1 | +## 3.0.0-beta.2 |
| 2 | Breaking changes: | 2 | Breaking changes: |
| 3 | +* The arguments parameter of onDetect is removed. The data is now returned by the onStart callback | ||
| 4 | +in the MobileScanner widget. | ||
| 5 | +* allowDuplicates is removed and replaced by MobileScannerSpeed enum. | ||
| 6 | +* onPermissionSet in MobileScanner widget is deprecated and will be removed. Use the onPermissionSet | ||
| 7 | +onPermissionSet callback in MobileScannerController instead. | ||
| 3 | * [iOS] The minimum deployment target is now 11.0 or higher. | 8 | * [iOS] The minimum deployment target is now 11.0 or higher. |
| 9 | + | ||
| 10 | +Features: | ||
| 11 | +* The returnImage is working for both iOS and Android. You can enable it in the MobileScannerController. | ||
| 12 | +The image will be returned in the BarcodeCapture object provided by onDetect. | ||
| 13 | +* You can now control the DetectionSpeed, as well as the timeout of the DetectionSpeed. For more | ||
| 14 | +info see the DetectionSpeed documentation. This replaces the allowDuplicates function. | ||
| 15 | + | ||
| 16 | +Other improvements: | ||
| 17 | +* Both the [iOS] and [Android] codebases have been refactored completely. | ||
| 4 | * [iOS] Updated POD dependencies | 18 | * [iOS] Updated POD dependencies |
| 5 | 19 | ||
| 6 | ## 3.0.0-beta.1 | 20 | ## 3.0.0-beta.1 |
| @@ -19,6 +19,12 @@ class _BarcodeListScannerWithControllerState | @@ -19,6 +19,12 @@ class _BarcodeListScannerWithControllerState | ||
| 19 | torchEnabled: true, | 19 | torchEnabled: true, |
| 20 | // formats: [BarcodeFormat.qrCode] | 20 | // formats: [BarcodeFormat.qrCode] |
| 21 | // facing: CameraFacing.front, | 21 | // facing: CameraFacing.front, |
| 22 | + onPermissionSet: (hasPermission) { | ||
| 23 | + // Do something with permission callback | ||
| 24 | + }, | ||
| 25 | + // detectionSpeed: DetectionSpeed.normal | ||
| 26 | + // detectionTimeoutMs: 1000, | ||
| 27 | + // returnImage: false, | ||
| 22 | ); | 28 | ); |
| 23 | 29 | ||
| 24 | bool isStarted = true; | 30 | bool isStarted = true; |
| @@ -34,16 +40,18 @@ class _BarcodeListScannerWithControllerState | @@ -34,16 +40,18 @@ class _BarcodeListScannerWithControllerState | ||
| 34 | MobileScanner( | 40 | MobileScanner( |
| 35 | controller: controller, | 41 | controller: controller, |
| 36 | fit: BoxFit.contain, | 42 | fit: BoxFit.contain, |
| 37 | - // allowDuplicates: true, | ||
| 38 | // controller: MobileScannerController( | 43 | // controller: MobileScannerController( |
| 39 | // torchEnabled: true, | 44 | // torchEnabled: true, |
| 40 | // facing: CameraFacing.front, | 45 | // facing: CameraFacing.front, |
| 41 | // ), | 46 | // ), |
| 42 | - onDetect: (barcodeCapture, arguments) { | 47 | + onDetect: (barcodeCapture) { |
| 43 | setState(() { | 48 | setState(() { |
| 44 | this.barcodeCapture = barcodeCapture; | 49 | this.barcodeCapture = barcodeCapture; |
| 45 | }); | 50 | }); |
| 46 | }, | 51 | }, |
| 52 | + onStart: (arguments) { | ||
| 53 | + // Do something with start arguments | ||
| 54 | + }, | ||
| 47 | ), | 55 | ), |
| 48 | Align( | 56 | Align( |
| 49 | alignment: Alignment.bottomCenter, | 57 | alignment: Alignment.bottomCenter, |
| @@ -16,9 +16,15 @@ class _BarcodeScannerWithControllerState | @@ -16,9 +16,15 @@ class _BarcodeScannerWithControllerState | ||
| 16 | BarcodeCapture? barcode; | 16 | BarcodeCapture? barcode; |
| 17 | 17 | ||
| 18 | MobileScannerController controller = MobileScannerController( | 18 | MobileScannerController controller = MobileScannerController( |
| 19 | - torchEnabled: true, detectionSpeed: DetectionSpeed.unrestricted, | 19 | + torchEnabled: true, |
| 20 | // formats: [BarcodeFormat.qrCode] | 20 | // formats: [BarcodeFormat.qrCode] |
| 21 | // facing: CameraFacing.front, | 21 | // facing: CameraFacing.front, |
| 22 | + onPermissionSet: (hasPermission) { | ||
| 23 | + // Do something with permission callback | ||
| 24 | + }, | ||
| 25 | + // detectionSpeed: DetectionSpeed.normal | ||
| 26 | + // detectionTimeoutMs: 1000, | ||
| 27 | + // returnImage: false, | ||
| 22 | ); | 28 | ); |
| 23 | 29 | ||
| 24 | bool isStarted = true; | 30 | bool isStarted = true; |
| @@ -34,12 +40,11 @@ class _BarcodeScannerWithControllerState | @@ -34,12 +40,11 @@ class _BarcodeScannerWithControllerState | ||
| 34 | MobileScanner( | 40 | MobileScanner( |
| 35 | controller: controller, | 41 | controller: controller, |
| 36 | fit: BoxFit.contain, | 42 | fit: BoxFit.contain, |
| 37 | - // allowDuplicates: true, | ||
| 38 | // controller: MobileScannerController( | 43 | // controller: MobileScannerController( |
| 39 | // torchEnabled: true, | 44 | // torchEnabled: true, |
| 40 | // facing: CameraFacing.front, | 45 | // facing: CameraFacing.front, |
| 41 | // ), | 46 | // ), |
| 42 | - onDetect: (barcode, args) { | 47 | + onDetect: (barcode) { |
| 43 | setState(() { | 48 | setState(() { |
| 44 | this.barcode = barcode; | 49 | this.barcode = barcode; |
| 45 | }); | 50 | }); |
| @@ -18,10 +18,15 @@ class _BarcodeScannerReturningImageState | @@ -18,10 +18,15 @@ class _BarcodeScannerReturningImageState | ||
| 18 | MobileScannerArguments? arguments; | 18 | MobileScannerArguments? arguments; |
| 19 | 19 | ||
| 20 | MobileScannerController controller = MobileScannerController( | 20 | MobileScannerController controller = MobileScannerController( |
| 21 | - // torchEnabled: true, | ||
| 22 | - returnImage: true, | 21 | + torchEnabled: true, |
| 23 | // formats: [BarcodeFormat.qrCode] | 22 | // formats: [BarcodeFormat.qrCode] |
| 24 | // facing: CameraFacing.front, | 23 | // facing: CameraFacing.front, |
| 24 | + onPermissionSet: (hasPermission) { | ||
| 25 | + // Do something with permission callback | ||
| 26 | + }, | ||
| 27 | + // detectionSpeed: DetectionSpeed.normal | ||
| 28 | + // detectionTimeoutMs: 1000, | ||
| 29 | + returnImage: true, | ||
| 25 | ); | 30 | ); |
| 26 | 31 | ||
| 27 | bool isStarted = true; | 32 | bool isStarted = true; |
| @@ -29,150 +34,140 @@ class _BarcodeScannerReturningImageState | @@ -29,150 +34,140 @@ class _BarcodeScannerReturningImageState | ||
| 29 | @override | 34 | @override |
| 30 | Widget build(BuildContext context) { | 35 | Widget build(BuildContext context) { |
| 31 | return Scaffold( | 36 | return Scaffold( |
| 32 | - backgroundColor: Colors.black, | ||
| 33 | - body: Builder( | ||
| 34 | - builder: (context) { | ||
| 35 | - return Column( | ||
| 36 | - children: [ | ||
| 37 | - Container( | ||
| 38 | - color: Colors.blueGrey, | ||
| 39 | - width: double.infinity, | ||
| 40 | - height: 0.33 * MediaQuery.of(context).size.height, | ||
| 41 | - child: barcode?.image != null | ||
| 42 | - ? Transform.rotate( | ||
| 43 | - angle: 90 * pi / 180, | ||
| 44 | - child: Image( | ||
| 45 | - gaplessPlayback: true, | ||
| 46 | - image: MemoryImage(barcode!.image!), | ||
| 47 | - fit: BoxFit.contain, | ||
| 48 | - ), | ||
| 49 | - ) | ||
| 50 | - : const ColoredBox( | ||
| 51 | - color: Colors.white, | ||
| 52 | - child: Center( | ||
| 53 | - child: Text( | ||
| 54 | - 'Your scanned barcode will appear here!', | ||
| 55 | - ), | ||
| 56 | - ), | ||
| 57 | - ), | ||
| 58 | - ), | ||
| 59 | - Container( | ||
| 60 | - height: 0.66 * MediaQuery.of(context).size.height, | ||
| 61 | - color: Colors.grey, | ||
| 62 | - child: Stack( | ||
| 63 | - children: [ | ||
| 64 | - MobileScanner( | ||
| 65 | - controller: controller, | 37 | + body: SafeArea( |
| 38 | + child: Column( | ||
| 39 | + children: [ | ||
| 40 | + Expanded( | ||
| 41 | + child: barcode?.image != null | ||
| 42 | + ? Transform.rotate( | ||
| 43 | + angle: 90 * pi / 180, | ||
| 44 | + child: Image( | ||
| 45 | + gaplessPlayback: true, | ||
| 46 | + image: MemoryImage(barcode!.image!), | ||
| 66 | fit: BoxFit.contain, | 47 | fit: BoxFit.contain, |
| 67 | - // allowDuplicates: true, | ||
| 68 | - // controller: MobileScannerController( | ||
| 69 | - // torchEnabled: true, | ||
| 70 | - // facing: CameraFacing.front, | ||
| 71 | - // ), | ||
| 72 | - onDetect: (barcode, arguments) { | ||
| 73 | - setState(() { | ||
| 74 | - this.arguments = arguments; | ||
| 75 | - this.barcode = barcode; | ||
| 76 | - }); | ||
| 77 | - }, | ||
| 78 | ), | 48 | ), |
| 79 | - Align( | 49 | + ) |
| 50 | + : const Center( | ||
| 51 | + child: Text( | ||
| 52 | + 'Your scanned barcode will appear here!', | ||
| 53 | + ), | ||
| 54 | + ), | ||
| 55 | + ), | ||
| 56 | + Expanded( | ||
| 57 | + flex: 2, | ||
| 58 | + child: ColoredBox( | ||
| 59 | + color: Colors.grey, | ||
| 60 | + child: Stack( | ||
| 61 | + children: [ | ||
| 62 | + MobileScanner( | ||
| 63 | + controller: controller, | ||
| 64 | + fit: BoxFit.contain, | ||
| 65 | + // controller: MobileScannerController( | ||
| 66 | + // torchEnabled: true, | ||
| 67 | + // facing: CameraFacing.front, | ||
| 68 | + // ), | ||
| 69 | + onDetect: (barcode) { | ||
| 70 | + setState(() { | ||
| 71 | + this.barcode = barcode; | ||
| 72 | + }); | ||
| 73 | + }, | ||
| 74 | + ), | ||
| 75 | + Align( | ||
| 76 | + alignment: Alignment.bottomCenter, | ||
| 77 | + child: Container( | ||
| 80 | alignment: Alignment.bottomCenter, | 78 | alignment: Alignment.bottomCenter, |
| 81 | - child: Container( | ||
| 82 | - alignment: Alignment.bottomCenter, | ||
| 83 | - height: 100, | ||
| 84 | - color: Colors.black.withOpacity(0.4), | ||
| 85 | - child: Row( | ||
| 86 | - mainAxisAlignment: MainAxisAlignment.spaceEvenly, | ||
| 87 | - children: [ | ||
| 88 | - IconButton( | ||
| 89 | - color: Colors.white, | ||
| 90 | - icon: ValueListenableBuilder( | ||
| 91 | - valueListenable: controller.torchState, | ||
| 92 | - builder: (context, state, child) { | ||
| 93 | - if (state == null) { | 79 | + height: 100, |
| 80 | + color: Colors.black.withOpacity(0.4), | ||
| 81 | + child: Row( | ||
| 82 | + mainAxisAlignment: MainAxisAlignment.spaceEvenly, | ||
| 83 | + children: [ | ||
| 84 | + IconButton( | ||
| 85 | + color: Colors.white, | ||
| 86 | + icon: ValueListenableBuilder( | ||
| 87 | + valueListenable: controller.torchState, | ||
| 88 | + builder: (context, state, child) { | ||
| 89 | + if (state == null) { | ||
| 90 | + return const Icon( | ||
| 91 | + Icons.flash_off, | ||
| 92 | + color: Colors.grey, | ||
| 93 | + ); | ||
| 94 | + } | ||
| 95 | + switch (state as TorchState) { | ||
| 96 | + case TorchState.off: | ||
| 94 | return const Icon( | 97 | return const Icon( |
| 95 | Icons.flash_off, | 98 | Icons.flash_off, |
| 96 | color: Colors.grey, | 99 | color: Colors.grey, |
| 97 | ); | 100 | ); |
| 98 | - } | ||
| 99 | - switch (state as TorchState) { | ||
| 100 | - case TorchState.off: | ||
| 101 | - return const Icon( | ||
| 102 | - Icons.flash_off, | ||
| 103 | - color: Colors.grey, | ||
| 104 | - ); | ||
| 105 | - case TorchState.on: | ||
| 106 | - return const Icon( | ||
| 107 | - Icons.flash_on, | ||
| 108 | - color: Colors.yellow, | ||
| 109 | - ); | ||
| 110 | - } | ||
| 111 | - }, | ||
| 112 | - ), | ||
| 113 | - iconSize: 32.0, | ||
| 114 | - onPressed: () => controller.toggleTorch(), | ||
| 115 | - ), | ||
| 116 | - IconButton( | ||
| 117 | - color: Colors.white, | ||
| 118 | - icon: isStarted | ||
| 119 | - ? const Icon(Icons.stop) | ||
| 120 | - : const Icon(Icons.play_arrow), | ||
| 121 | - iconSize: 32.0, | ||
| 122 | - onPressed: () => setState(() { | ||
| 123 | - isStarted | ||
| 124 | - ? controller.stop() | ||
| 125 | - : controller.start(); | ||
| 126 | - isStarted = !isStarted; | ||
| 127 | - }), | 101 | + case TorchState.on: |
| 102 | + return const Icon( | ||
| 103 | + Icons.flash_on, | ||
| 104 | + color: Colors.yellow, | ||
| 105 | + ); | ||
| 106 | + } | ||
| 107 | + }, | ||
| 128 | ), | 108 | ), |
| 129 | - Center( | ||
| 130 | - child: SizedBox( | ||
| 131 | - width: MediaQuery.of(context).size.width - 200, | ||
| 132 | - height: 50, | ||
| 133 | - child: FittedBox( | ||
| 134 | - child: Text( | ||
| 135 | - barcode?.barcodes.first.rawValue ?? | ||
| 136 | - 'Scan something!', | ||
| 137 | - overflow: TextOverflow.fade, | ||
| 138 | - style: Theme.of(context) | ||
| 139 | - .textTheme | ||
| 140 | - .headline4! | ||
| 141 | - .copyWith(color: Colors.white), | ||
| 142 | - ), | 109 | + iconSize: 32.0, |
| 110 | + onPressed: () => controller.toggleTorch(), | ||
| 111 | + ), | ||
| 112 | + IconButton( | ||
| 113 | + color: Colors.white, | ||
| 114 | + icon: isStarted | ||
| 115 | + ? const Icon(Icons.stop) | ||
| 116 | + : const Icon(Icons.play_arrow), | ||
| 117 | + iconSize: 32.0, | ||
| 118 | + onPressed: () => setState(() { | ||
| 119 | + isStarted | ||
| 120 | + ? controller.stop() | ||
| 121 | + : controller.start(); | ||
| 122 | + isStarted = !isStarted; | ||
| 123 | + }), | ||
| 124 | + ), | ||
| 125 | + Center( | ||
| 126 | + child: SizedBox( | ||
| 127 | + width: MediaQuery.of(context).size.width - 200, | ||
| 128 | + height: 50, | ||
| 129 | + child: FittedBox( | ||
| 130 | + child: Text( | ||
| 131 | + barcode?.barcodes.first.rawValue ?? | ||
| 132 | + 'Scan something!', | ||
| 133 | + overflow: TextOverflow.fade, | ||
| 134 | + style: Theme.of(context) | ||
| 135 | + .textTheme | ||
| 136 | + .headline4! | ||
| 137 | + .copyWith(color: Colors.white), | ||
| 143 | ), | 138 | ), |
| 144 | ), | 139 | ), |
| 145 | ), | 140 | ), |
| 146 | - IconButton( | ||
| 147 | - color: Colors.white, | ||
| 148 | - icon: ValueListenableBuilder( | ||
| 149 | - valueListenable: controller.cameraFacingState, | ||
| 150 | - builder: (context, state, child) { | ||
| 151 | - if (state == null) { | 141 | + ), |
| 142 | + IconButton( | ||
| 143 | + color: Colors.white, | ||
| 144 | + icon: ValueListenableBuilder( | ||
| 145 | + valueListenable: controller.cameraFacingState, | ||
| 146 | + builder: (context, state, child) { | ||
| 147 | + if (state == null) { | ||
| 148 | + return const Icon(Icons.camera_front); | ||
| 149 | + } | ||
| 150 | + switch (state as CameraFacing) { | ||
| 151 | + case CameraFacing.front: | ||
| 152 | return const Icon(Icons.camera_front); | 152 | return const Icon(Icons.camera_front); |
| 153 | - } | ||
| 154 | - switch (state as CameraFacing) { | ||
| 155 | - case CameraFacing.front: | ||
| 156 | - return const Icon(Icons.camera_front); | ||
| 157 | - case CameraFacing.back: | ||
| 158 | - return const Icon(Icons.camera_rear); | ||
| 159 | - } | ||
| 160 | - }, | ||
| 161 | - ), | ||
| 162 | - iconSize: 32.0, | ||
| 163 | - onPressed: () => controller.switchCamera(), | 153 | + case CameraFacing.back: |
| 154 | + return const Icon(Icons.camera_rear); | ||
| 155 | + } | ||
| 156 | + }, | ||
| 164 | ), | 157 | ), |
| 165 | - ], | ||
| 166 | - ), | 158 | + iconSize: 32.0, |
| 159 | + onPressed: () => controller.switchCamera(), | ||
| 160 | + ), | ||
| 161 | + ], | ||
| 167 | ), | 162 | ), |
| 168 | ), | 163 | ), |
| 169 | - ], | ||
| 170 | - ), | 164 | + ), |
| 165 | + ], | ||
| 171 | ), | 166 | ), |
| 172 | - ], | ||
| 173 | - ); | ||
| 174 | - }, | 167 | + ), |
| 168 | + ), | ||
| 169 | + ], | ||
| 175 | ), | 170 | ), |
| 176 | - ); | 171 | + ),); |
| 177 | } | 172 | } |
| 178 | } | 173 | } |
| @@ -24,8 +24,7 @@ class _BarcodeScannerWithoutControllerState | @@ -24,8 +24,7 @@ class _BarcodeScannerWithoutControllerState | ||
| 24 | children: [ | 24 | children: [ |
| 25 | MobileScanner( | 25 | MobileScanner( |
| 26 | fit: BoxFit.contain, | 26 | fit: BoxFit.contain, |
| 27 | - // allowDuplicates: false, | ||
| 28 | - onDetect: (capture, arguments) { | 27 | + onDetect: (capture) { |
| 29 | setState(() { | 28 | setState(() { |
| 30 | this.capture = capture; | 29 | this.capture = capture; |
| 31 | }); | 30 | }); |
| 1 | library mobile_scanner; | 1 | library mobile_scanner; |
| 2 | 2 | ||
| 3 | -export 'src/barcode.dart'; | ||
| 4 | -export 'src/barcode_capture.dart'; | ||
| 5 | export 'src/enums/camera_facing.dart'; | 3 | export 'src/enums/camera_facing.dart'; |
| 6 | export 'src/enums/detection_speed.dart'; | 4 | export 'src/enums/detection_speed.dart'; |
| 7 | export 'src/enums/mobile_scanner_state.dart'; | 5 | export 'src/enums/mobile_scanner_state.dart'; |
| 8 | export 'src/enums/ratio.dart'; | 6 | export 'src/enums/ratio.dart'; |
| 9 | export 'src/enums/torch_state.dart'; | 7 | export 'src/enums/torch_state.dart'; |
| 10 | export 'src/mobile_scanner.dart'; | 8 | export 'src/mobile_scanner.dart'; |
| 11 | -export 'src/mobile_scanner_arguments.dart'; | ||
| 12 | export 'src/mobile_scanner_controller.dart'; | 9 | export 'src/mobile_scanner_controller.dart'; |
| 10 | +export 'src/objects/barcode.dart'; | ||
| 11 | +export 'src/objects/barcode_capture.dart'; | ||
| 12 | +export 'src/objects/mobile_scanner_arguments.dart'; |
| @@ -2,11 +2,19 @@ | @@ -2,11 +2,19 @@ | ||
| 2 | enum DetectionSpeed { | 2 | enum DetectionSpeed { |
| 3 | /// The scanner will only scan a barcode once, and never again until another | 3 | /// The scanner will only scan a barcode once, and never again until another |
| 4 | /// barcode has been scanned. | 4 | /// barcode has been scanned. |
| 5 | + /// | ||
| 6 | + /// NOTE: This mode does analyze every frame in order to check if the value | ||
| 7 | + /// has changed. | ||
| 5 | noDuplicates, | 8 | noDuplicates, |
| 6 | 9 | ||
| 7 | - /// The barcode scanner will wait | 10 | + /// The barcode scanner will scan one barcode, and wait 250 Miliseconds before |
| 11 | + /// scanning again. This will prevent memory issues on older devices. | ||
| 12 | + /// | ||
| 13 | + /// You can change the timeout duration with [detectionTimeout] parameter. | ||
| 8 | normal, | 14 | normal, |
| 9 | 15 | ||
| 10 | - /// Back facing camera. | 16 | + /// Let the scanner detect barcodes without restriction. |
| 17 | + /// | ||
| 18 | + /// NOTE: This can cause memory issues with older devices. | ||
| 11 | unrestricted, | 19 | unrestricted, |
| 12 | } | 20 | } |
| 1 | -enum MobileScannerState { undetermined, authorized, denied } | 1 | +/// The authorization state of the scanner. |
| 2 | +enum MobileScannerState { | ||
| 3 | + /// The scanner has yet to request weather it is [authorized] or [denied] | ||
| 4 | + undetermined, | ||
| 5 | + | ||
| 6 | + /// The scanner has the required permissions. | ||
| 7 | + authorized, | ||
| 8 | + | ||
| 9 | + /// The user denied the required permissions. | ||
| 10 | + denied | ||
| 11 | +} |
| 1 | import 'package:flutter/foundation.dart'; | 1 | import 'package:flutter/foundation.dart'; |
| 2 | import 'package:flutter/material.dart'; | 2 | import 'package:flutter/material.dart'; |
| 3 | -import 'package:mobile_scanner/src/barcode_capture.dart'; | ||
| 4 | -import 'package:mobile_scanner/src/mobile_scanner_arguments.dart'; | ||
| 5 | import 'package:mobile_scanner/src/mobile_scanner_controller.dart'; | 3 | import 'package:mobile_scanner/src/mobile_scanner_controller.dart'; |
| 4 | +import 'package:mobile_scanner/src/objects/barcode_capture.dart'; | ||
| 5 | +import 'package:mobile_scanner/src/objects/mobile_scanner_arguments.dart'; | ||
| 6 | + | ||
| 7 | +typedef MobileScannerCallback = void Function(BarcodeCapture barcodes); | ||
| 8 | +typedef MobileScannerArgumentsCallback = void Function( | ||
| 9 | + MobileScannerArguments? arguments, | ||
| 10 | +); | ||
| 6 | 11 | ||
| 7 | /// A widget showing a live camera preview. | 12 | /// A widget showing a live camera preview. |
| 8 | class MobileScanner extends StatefulWidget { | 13 | class MobileScanner extends StatefulWidget { |
| @@ -10,14 +15,21 @@ class MobileScanner extends StatefulWidget { | @@ -10,14 +15,21 @@ class MobileScanner extends StatefulWidget { | ||
| 10 | final MobileScannerController? controller; | 15 | final MobileScannerController? controller; |
| 11 | 16 | ||
| 12 | /// Calls the provided [onPermissionSet] callback when the permission is set. | 17 | /// Calls the provided [onPermissionSet] callback when the permission is set. |
| 18 | + // @Deprecated('Use the [onPermissionSet] paremeter in the [MobileScannerController] instead.') | ||
| 19 | + // ignore: deprecated_consistency | ||
| 13 | final Function(bool permissionGranted)? onPermissionSet; | 20 | final Function(bool permissionGranted)? onPermissionSet; |
| 14 | 21 | ||
| 15 | /// Function that gets called when a Barcode is detected. | 22 | /// Function that gets called when a Barcode is detected. |
| 16 | /// | 23 | /// |
| 17 | /// [barcode] The barcode object with all information about the scanned code. | 24 | /// [barcode] The barcode object with all information about the scanned code. |
| 18 | - /// [startArguments] Information about the state of the MobileScanner widget | ||
| 19 | - final Function(BarcodeCapture capture, MobileScannerArguments? arguments) | ||
| 20 | - onDetect; | 25 | + /// [startInternalArguments] Information about the state of the MobileScanner widget |
| 26 | + final MobileScannerCallback onDetect; | ||
| 27 | + | ||
| 28 | + /// Function that gets called when the scanner is started. | ||
| 29 | + /// | ||
| 30 | + /// [arguments] The start arguments of the scanner. This contains the size of | ||
| 31 | + /// the scanner which can be used to draw a box over the scanner. | ||
| 32 | + final MobileScannerArgumentsCallback? onStart; | ||
| 21 | 33 | ||
| 22 | /// Handles how the widget should fit the screen. | 34 | /// Handles how the widget should fit the screen. |
| 23 | final BoxFit fit; | 35 | final BoxFit fit; |
| @@ -29,10 +41,12 @@ class MobileScanner extends StatefulWidget { | @@ -29,10 +41,12 @@ class MobileScanner extends StatefulWidget { | ||
| 29 | const MobileScanner({ | 41 | const MobileScanner({ |
| 30 | super.key, | 42 | super.key, |
| 31 | required this.onDetect, | 43 | required this.onDetect, |
| 44 | + this.onStart, | ||
| 32 | this.controller, | 45 | this.controller, |
| 33 | this.autoResume = true, | 46 | this.autoResume = true, |
| 34 | this.fit = BoxFit.cover, | 47 | this.fit = BoxFit.cover, |
| 35 | - this.onPermissionSet, | 48 | + @Deprecated('Use the [onPermissionSet] paremeter in the [MobileScannerController] instead.') |
| 49 | + this.onPermissionSet, | ||
| 36 | }); | 50 | }); |
| 37 | 51 | ||
| 38 | @override | 52 | @override |
| @@ -49,7 +63,14 @@ class _MobileScannerState extends State<MobileScanner> | @@ -49,7 +63,14 @@ class _MobileScannerState extends State<MobileScanner> | ||
| 49 | WidgetsBinding.instance.addObserver(this); | 63 | WidgetsBinding.instance.addObserver(this); |
| 50 | controller = widget.controller ?? | 64 | controller = widget.controller ?? |
| 51 | MobileScannerController(onPermissionSet: widget.onPermissionSet); | 65 | MobileScannerController(onPermissionSet: widget.onPermissionSet); |
| 52 | - if (!controller.isStarting) controller.start(); | 66 | + if (!controller.isStarting) { |
| 67 | + _startScanner(); | ||
| 68 | + } | ||
| 69 | + } | ||
| 70 | + | ||
| 71 | + Future<void> _startScanner() async { | ||
| 72 | + final arguments = await controller.start(); | ||
| 73 | + widget.onStart?.call(arguments); | ||
| 53 | } | 74 | } |
| 54 | 75 | ||
| 55 | bool resumeFromBackground = false; | 76 | bool resumeFromBackground = false; |
| @@ -64,7 +85,7 @@ class _MobileScannerState extends State<MobileScanner> | @@ -64,7 +85,7 @@ class _MobileScannerState extends State<MobileScanner> | ||
| 64 | switch (state) { | 85 | switch (state) { |
| 65 | case AppLifecycleState.resumed: | 86 | case AppLifecycleState.resumed: |
| 66 | resumeFromBackground = false; | 87 | resumeFromBackground = false; |
| 67 | - controller.start(); | 88 | + _startScanner(); |
| 68 | break; | 89 | break; |
| 69 | case AppLifecycleState.paused: | 90 | case AppLifecycleState.paused: |
| 70 | resumeFromBackground = true; | 91 | resumeFromBackground = true; |
| @@ -87,7 +108,7 @@ class _MobileScannerState extends State<MobileScanner> | @@ -87,7 +108,7 @@ class _MobileScannerState extends State<MobileScanner> | ||
| 87 | return const ColoredBox(color: Colors.black); | 108 | return const ColoredBox(color: Colors.black); |
| 88 | } else { | 109 | } else { |
| 89 | controller.barcodes.listen((barcode) { | 110 | controller.barcodes.listen((barcode) { |
| 90 | - widget.onDetect(barcode, value! as MobileScannerArguments); | 111 | + widget.onDetect(barcode); |
| 91 | }); | 112 | }); |
| 92 | return ClipRect( | 113 | return ClipRect( |
| 93 | child: SizedBox( | 114 | child: SizedBox( |
| @@ -30,7 +30,8 @@ class MobileScannerController { | @@ -30,7 +30,8 @@ class MobileScannerController { | ||
| 30 | .listen((data) => _handleEvent(data as Map)); | 30 | .listen((data) => _handleEvent(data as Map)); |
| 31 | } | 31 | } |
| 32 | 32 | ||
| 33 | - //Must be static to keep the same value on new instances | 33 | + /// The hashcode of the controller to check if the correct object is mounted. |
| 34 | + /// Must be static to keep the same value on new instances | ||
| 34 | static int? controllerHashcode; | 35 | static int? controllerHashcode; |
| 35 | 36 | ||
| 36 | /// Select which camera should be used. | 37 | /// Select which camera should be used. |
| @@ -56,6 +57,11 @@ class MobileScannerController { | @@ -56,6 +57,11 @@ class MobileScannerController { | ||
| 56 | /// WARNING: DetectionSpeed.unrestricted can cause memory issues on some devices | 57 | /// WARNING: DetectionSpeed.unrestricted can cause memory issues on some devices |
| 57 | final DetectionSpeed detectionSpeed; | 58 | final DetectionSpeed detectionSpeed; |
| 58 | 59 | ||
| 60 | + /// Sets the timeout of scanner. | ||
| 61 | + /// The timeout is set in miliseconds. | ||
| 62 | + /// | ||
| 63 | + /// NOTE: The timeout only works if the [detectionSpeed] is set to | ||
| 64 | + /// [DetectionSpeed.normal] (which is the default value). | ||
| 59 | final int detectionTimeoutMs; | 65 | final int detectionTimeoutMs; |
| 60 | 66 | ||
| 61 | /// Sets the barcode stream | 67 | /// Sets the barcode stream |
| @@ -85,6 +91,7 @@ class MobileScannerController { | @@ -85,6 +91,7 @@ class MobileScannerController { | ||
| 85 | ValueNotifier(facing); | 91 | ValueNotifier(facing); |
| 86 | 92 | ||
| 87 | bool isStarting = false; | 93 | bool isStarting = false; |
| 94 | + | ||
| 88 | bool? _hasTorch; | 95 | bool? _hasTorch; |
| 89 | 96 | ||
| 90 | /// Set the starting arguments for the camera | 97 | /// Set the starting arguments for the camera |
| @@ -113,9 +120,9 @@ class MobileScannerController { | @@ -113,9 +120,9 @@ class MobileScannerController { | ||
| 113 | Future<MobileScannerArguments?> start({ | 120 | Future<MobileScannerArguments?> start({ |
| 114 | CameraFacing? cameraFacingOverride, | 121 | CameraFacing? cameraFacingOverride, |
| 115 | }) async { | 122 | }) async { |
| 116 | - debugPrint('Hashcode controller: $hashCode'); | ||
| 117 | if (isStarting) { | 123 | if (isStarting) { |
| 118 | debugPrint("Called start() while starting."); | 124 | debugPrint("Called start() while starting."); |
| 125 | + return null; | ||
| 119 | } | 126 | } |
| 120 | isStarting = true; | 127 | isStarting = true; |
| 121 | 128 | ||
| @@ -152,10 +159,10 @@ class MobileScannerController { | @@ -152,10 +159,10 @@ class MobileScannerController { | ||
| 152 | ); | 159 | ); |
| 153 | } on PlatformException catch (error) { | 160 | } on PlatformException catch (error) { |
| 154 | debugPrint('${error.code}: ${error.message}'); | 161 | debugPrint('${error.code}: ${error.message}'); |
| 155 | - isStarting = false; | ||
| 156 | if (error.code == "MobileScannerWeb") { | 162 | if (error.code == "MobileScannerWeb") { |
| 157 | onPermissionSet?.call(false); | 163 | onPermissionSet?.call(false); |
| 158 | } | 164 | } |
| 165 | + isStarting = false; | ||
| 159 | return null; | 166 | return null; |
| 160 | } | 167 | } |
| 161 | 168 | ||
| @@ -172,27 +179,24 @@ class MobileScannerController { | @@ -172,27 +179,24 @@ class MobileScannerController { | ||
| 172 | } | 179 | } |
| 173 | 180 | ||
| 174 | if (kIsWeb) { | 181 | if (kIsWeb) { |
| 182 | + // If we reach this line, it means camera permission has been granted | ||
| 175 | onPermissionSet?.call( | 183 | onPermissionSet?.call( |
| 176 | true, | 184 | true, |
| 177 | - ); // If we reach this line, it means camera permission has been granted | ||
| 178 | - | ||
| 179 | - startArguments.value = MobileScannerArguments( | ||
| 180 | - webId: startResult['ViewID'] as String?, | ||
| 181 | - size: Size( | ||
| 182 | - startResult['videoWidth'] as double? ?? 0, | ||
| 183 | - startResult['videoHeight'] as double? ?? 0, | ||
| 184 | - ), | ||
| 185 | - hasTorch: _hasTorch!, | ||
| 186 | - ); | ||
| 187 | - } else { | ||
| 188 | - startArguments.value = MobileScannerArguments( | ||
| 189 | - textureId: startResult['textureId'] as int?, | ||
| 190 | - size: toSize(startResult['size'] as Map? ?? {}), | ||
| 191 | - hasTorch: _hasTorch!, | ||
| 192 | ); | 185 | ); |
| 193 | } | 186 | } |
| 187 | + | ||
| 194 | isStarting = false; | 188 | isStarting = false; |
| 195 | - return startArguments.value!; | 189 | + return startArguments.value = MobileScannerArguments( |
| 190 | + size: kIsWeb | ||
| 191 | + ? Size( | ||
| 192 | + startResult['videoWidth'] as double? ?? 0, | ||
| 193 | + startResult['videoHeight'] as double? ?? 0, | ||
| 194 | + ) | ||
| 195 | + : toSize(startResult['size'] as Map? ?? {}), | ||
| 196 | + hasTorch: _hasTorch!, | ||
| 197 | + textureId: kIsWeb ? null : startResult['textureId'] as int?, | ||
| 198 | + webId: kIsWeb ? startResult['ViewID'] as String? : null, | ||
| 199 | + ); | ||
| 196 | } | 200 | } |
| 197 | 201 | ||
| 198 | /// Stops the camera, but does not dispose this controller. | 202 | /// Stops the camera, but does not dispose this controller. |
| @@ -12,11 +12,6 @@ class Barcode { | @@ -12,11 +12,6 @@ class Barcode { | ||
| 12 | /// Returns null if the corner points can not be determined. | 12 | /// Returns null if the corner points can not be determined. |
| 13 | final List<Offset>? corners; | 13 | final List<Offset>? corners; |
| 14 | 14 | ||
| 15 | - /// Returns raw bytes of the image buffer | ||
| 16 | - /// | ||
| 17 | - /// Returns null if the image was not returned | ||
| 18 | - final Uint8List? image; | ||
| 19 | - | ||
| 20 | /// Returns barcode format | 15 | /// Returns barcode format |
| 21 | final BarcodeFormat format; | 16 | final BarcodeFormat format; |
| 22 | 17 | ||
| @@ -79,7 +74,6 @@ class Barcode { | @@ -79,7 +74,6 @@ class Barcode { | ||
| 79 | 74 | ||
| 80 | Barcode({ | 75 | Barcode({ |
| 81 | this.corners, | 76 | this.corners, |
| 82 | - this.image, | ||
| 83 | this.format = BarcodeFormat.ean13, | 77 | this.format = BarcodeFormat.ean13, |
| 84 | this.rawBytes, | 78 | this.rawBytes, |
| 85 | this.type = BarcodeType.text, | 79 | this.type = BarcodeType.text, |
| @@ -97,7 +91,7 @@ class Barcode { | @@ -97,7 +91,7 @@ class Barcode { | ||
| 97 | }); | 91 | }); |
| 98 | 92 | ||
| 99 | /// Create a [Barcode] from native data. | 93 | /// Create a [Barcode] from native data. |
| 100 | - Barcode.fromNative(Map data, {this.image}) | 94 | + Barcode.fromNative(Map data) |
| 101 | : corners = toCorners(data['corners'] as List?), | 95 | : corners = toCorners(data['corners'] as List?), |
| 102 | format = toFormat(data['format'] as int), | 96 | format = toFormat(data['format'] as int), |
| 103 | rawBytes = data['rawBytes'] as Uint8List?, | 97 | rawBytes = data['rawBytes'] as Uint8List?, |
| 1 | import 'dart:typed_data'; | 1 | import 'dart:typed_data'; |
| 2 | 2 | ||
| 3 | -import 'package:mobile_scanner/src/barcode.dart'; | 3 | +import 'package:mobile_scanner/src/objects/barcode.dart'; |
| 4 | 4 | ||
| 5 | +/// The return object after a frame is scanned. | ||
| 6 | +/// | ||
| 7 | +/// [barcodes] A list with barcodes. A scanned frame can contain multiple | ||
| 8 | +/// barcodes. | ||
| 9 | +/// [image] If enabled, an image of the scanned frame. | ||
| 5 | class BarcodeCapture { | 10 | class BarcodeCapture { |
| 6 | - List<Barcode> barcodes; | ||
| 7 | - Uint8List? image; | 11 | + final List<Barcode> barcodes; |
| 12 | + | ||
| 13 | + final Uint8List? image; | ||
| 8 | 14 | ||
| 9 | BarcodeCapture({ | 15 | BarcodeCapture({ |
| 10 | required this.barcodes, | 16 | required this.barcodes, |
| 1 | import 'package:flutter/material.dart'; | 1 | import 'package:flutter/material.dart'; |
| 2 | 2 | ||
| 3 | -/// Camera args for [CameraView]. | 3 | +/// The start arguments of the scanner. |
| 4 | class MobileScannerArguments { | 4 | class MobileScannerArguments { |
| 5 | - /// The texture id. | ||
| 6 | - final int? textureId; | ||
| 7 | - | ||
| 8 | - /// Size of the texture. | 5 | + /// The output size of the camera. |
| 6 | + /// This value can be used to draw a box in the image. | ||
| 9 | final Size size; | 7 | final Size size; |
| 10 | 8 | ||
| 9 | + /// A bool which is true if the device has a torch. | ||
| 11 | final bool hasTorch; | 10 | final bool hasTorch; |
| 12 | 11 | ||
| 12 | + /// The texture id of the capture used internally. | ||
| 13 | + final int? textureId; | ||
| 14 | + | ||
| 15 | + /// The texture id of the capture used internally if device is web. | ||
| 13 | final String? webId; | 16 | final String? webId; |
| 14 | 17 | ||
| 15 | - /// Create a [MobileScannerArguments]. | ||
| 16 | MobileScannerArguments({ | 18 | MobileScannerArguments({ |
| 17 | - this.textureId, | ||
| 18 | required this.size, | 19 | required this.size, |
| 19 | required this.hasTorch, | 20 | required this.hasTorch, |
| 21 | + this.textureId, | ||
| 20 | this.webId, | 22 | this.webId, |
| 21 | }); | 23 | }); |
| 22 | } | 24 | } |
| 1 | name: mobile_scanner | 1 | name: mobile_scanner |
| 2 | description: A universal barcode and QR code scanner for Flutter based on MLKit. Uses CameraX on Android, AVFoundation on iOS and Apple Vision & AVFoundation on macOS. | 2 | description: A universal barcode and QR code scanner for Flutter based on MLKit. Uses CameraX on Android, AVFoundation on iOS and Apple Vision & AVFoundation on macOS. |
| 3 | -version: 3.0.0-beta.1 | 3 | +version: 3.0.0-beta.2 |
| 4 | repository: https://github.com/juliansteenbakker/mobile_scanner | 4 | repository: https://github.com/juliansteenbakker/mobile_scanner |
| 5 | 5 | ||
| 6 | environment: | 6 | environment: |
-
Please register or login to post a comment