Committed by
GitHub
Merge branch 'master' into dependabot/gradle/android/org.jetbrains.kotlin-kotlin-stdlib-jdk7-1.7.22
Showing
17 changed files
with
368 additions
and
193 deletions
| 1 | +## 3.0.0-beta.3 | ||
| 2 | +Deprecated: | ||
| 3 | +* The `onStart` method has been renamed to `onScannerStarted`. | ||
| 4 | +* The `onPermissionSet` argument of the `MobileScannerController` is now deprecated. | ||
| 5 | + | ||
| 6 | +Breaking changes: | ||
| 7 | +* `MobileScannerException` now uses an `errorCode` instead of a `message`. | ||
| 8 | +* `MobileScannerException` now contains additional details from the original error. | ||
| 9 | +* Refactored `MobileScannerController.start()` to throw `MobileScannerException`s | ||
| 10 | + with consistent error codes, rather than string messages. | ||
| 11 | + To handle permission errors, consider catching the result of `MobileScannerController.start()`. | ||
| 12 | +* The `autoResume` attribute has been removed from the `MobileScanner` widget. | ||
| 13 | + The controller already automatically resumes, so it had no effect. | ||
| 14 | +* Removed `MobileScannerCallback` and `MobileScannerArgumentsCallback` typedef. | ||
| 15 | + | ||
| 16 | +Improvements: | ||
| 17 | +* Toggling the device torch now does nothing if the device has no torch, rather than throwing an error. | ||
| 18 | +* Removed `called stop while already stopped` messages. | ||
| 19 | + | ||
| 20 | +Features: | ||
| 21 | +* Added a new `placeholderBuilder` function to the `MobileScanner` widget to customize the preview placeholder. | ||
| 22 | +* Added `autoStart` parameter to MobileScannerController(). If set to false, controller won't start automatically. | ||
| 23 | +* Added `hasTorch` function on MobileScannerController(). After starting the controller, you can check if the device has a torch. | ||
| 24 | + | ||
| 25 | +Fixes: | ||
| 26 | +* Fixes the missing gradle setup for the Android project, which prevented gradle sync from working. | ||
| 27 | +* Fixes `MobileScannerController.stop()` throwing when already stopped. | ||
| 28 | +* Fixes `MobileScannerController.toggleTorch()` throwing if the device has no torch. | ||
| 29 | + Now it does nothing if the torch is not available. | ||
| 30 | +* Fixes a memory leak where the `MobileScanner` would keep listening to the barcode events. | ||
| 31 | +* Fixes the `MobileScanner` preview depending on all attributes of `MediaQueryData`. | ||
| 32 | + Now it only depends on its layout constraints. | ||
| 33 | +* Fixed a potential crash when the scanner is restarted due to the app being resumed. | ||
| 34 | + | ||
| 1 | ## 3.0.0-beta.2 | 35 | ## 3.0.0-beta.2 |
| 2 | Breaking changes: | 36 | Breaking changes: |
| 3 | * The arguments parameter of onDetect is removed. The data is now returned by the onStart callback | 37 | * The arguments parameter of onDetect is removed. The data is now returned by the onStart callback |
| @@ -10,6 +10,7 @@ buildscript { | @@ -10,6 +10,7 @@ buildscript { | ||
| 10 | 10 | ||
| 11 | dependencies { | 11 | dependencies { |
| 12 | classpath 'com.android.tools.build:gradle:7.3.1' | 12 | classpath 'com.android.tools.build:gradle:7.3.1' |
| 13 | + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" | ||
| 13 | } | 14 | } |
| 14 | } | 15 | } |
| 15 | 16 |
| @@ -17,10 +17,9 @@ import com.google.mlkit.vision.barcode.BarcodeScanning | @@ -17,10 +17,9 @@ import com.google.mlkit.vision.barcode.BarcodeScanning | ||
| 17 | import com.google.mlkit.vision.common.InputImage | 17 | import com.google.mlkit.vision.common.InputImage |
| 18 | import dev.steenbakker.mobile_scanner.objects.DetectionSpeed | 18 | import dev.steenbakker.mobile_scanner.objects.DetectionSpeed |
| 19 | import dev.steenbakker.mobile_scanner.objects.MobileScannerStartParameters | 19 | import dev.steenbakker.mobile_scanner.objects.MobileScannerStartParameters |
| 20 | +import io.flutter.plugin.common.MethodChannel | ||
| 20 | import io.flutter.plugin.common.PluginRegistry | 21 | import io.flutter.plugin.common.PluginRegistry |
| 21 | import io.flutter.view.TextureRegistry | 22 | import io.flutter.view.TextureRegistry |
| 22 | - | ||
| 23 | -typealias PermissionCallback = (permissionGranted: Boolean) -> Unit | ||
| 24 | typealias MobileScannerCallback = (barcodes: List<Map<String, Any?>>, image: ByteArray?) -> Unit | 23 | typealias MobileScannerCallback = (barcodes: List<Map<String, Any?>>, image: ByteArray?) -> Unit |
| 25 | typealias AnalyzerCallback = (barcodes: List<Map<String, Any?>>?) -> Unit | 24 | typealias AnalyzerCallback = (barcodes: List<Map<String, Any?>>?) -> Unit |
| 26 | typealias MobileScannerErrorCallback = (error: String) -> Unit | 25 | typealias MobileScannerErrorCallback = (error: String) -> Unit |
| @@ -49,10 +48,9 @@ class MobileScanner( | @@ -49,10 +48,9 @@ class MobileScanner( | ||
| 49 | private const val REQUEST_CODE = 0x0786 | 48 | private const val REQUEST_CODE = 0x0786 |
| 50 | } | 49 | } |
| 51 | 50 | ||
| 52 | - private var listener: PluginRegistry.RequestPermissionsResultListener? = null | ||
| 53 | - | ||
| 54 | private var cameraProvider: ProcessCameraProvider? = null | 51 | private var cameraProvider: ProcessCameraProvider? = null |
| 55 | private var camera: Camera? = null | 52 | private var camera: Camera? = null |
| 53 | + private var pendingPermissionResult: MethodChannel.Result? = null | ||
| 56 | private var preview: Preview? = null | 54 | private var preview: Preview? = null |
| 57 | private var textureEntry: TextureRegistry.SurfaceTextureEntry? = null | 55 | private var textureEntry: TextureRegistry.SurfaceTextureEntry? = null |
| 58 | 56 | ||
| @@ -86,17 +84,12 @@ class MobileScanner( | @@ -86,17 +84,12 @@ class MobileScanner( | ||
| 86 | /** | 84 | /** |
| 87 | * Request camera permissions. | 85 | * Request camera permissions. |
| 88 | */ | 86 | */ |
| 89 | - fun requestPermission(permissionCallback: PermissionCallback) { | ||
| 90 | - listener | ||
| 91 | - ?: PluginRegistry.RequestPermissionsResultListener { requestCode, _, grantResults -> | ||
| 92 | - if (requestCode != REQUEST_CODE) { | ||
| 93 | - false | ||
| 94 | - } else { | ||
| 95 | - val authorized = grantResults[0] == PackageManager.PERMISSION_GRANTED | ||
| 96 | - permissionCallback(authorized) | ||
| 97 | - true | ||
| 98 | - } | 87 | + fun requestPermission(result: MethodChannel.Result) { |
| 88 | + if(pendingPermissionResult != null) { | ||
| 89 | + return | ||
| 99 | } | 90 | } |
| 91 | + | ||
| 92 | + pendingPermissionResult = result | ||
| 100 | val permissions = arrayOf(Manifest.permission.CAMERA) | 93 | val permissions = arrayOf(Manifest.permission.CAMERA) |
| 101 | ActivityCompat.requestPermissions(activity, permissions, REQUEST_CODE) | 94 | ActivityCompat.requestPermissions(activity, permissions, REQUEST_CODE) |
| 102 | } | 95 | } |
| @@ -109,7 +102,14 @@ class MobileScanner( | @@ -109,7 +102,14 @@ class MobileScanner( | ||
| 109 | permissions: Array<out String>, | 102 | permissions: Array<out String>, |
| 110 | grantResults: IntArray | 103 | grantResults: IntArray |
| 111 | ): Boolean { | 104 | ): Boolean { |
| 112 | - return listener?.onRequestPermissionsResult(requestCode, permissions, grantResults) ?: false | 105 | + if (requestCode != REQUEST_CODE) { |
| 106 | + return false | ||
| 107 | + } | ||
| 108 | + | ||
| 109 | + pendingPermissionResult?.success(grantResults[0] == PackageManager.PERMISSION_GRANTED) | ||
| 110 | + pendingPermissionResult = null | ||
| 111 | + | ||
| 112 | + return true | ||
| 113 | } | 113 | } |
| 114 | 114 | ||
| 115 | /** | 115 | /** |
| @@ -23,14 +23,8 @@ class MobileScannerPlugin : FlutterPlugin, ActivityAware, MethodChannel.MethodCa | @@ -23,14 +23,8 @@ class MobileScannerPlugin : FlutterPlugin, ActivityAware, MethodChannel.MethodCa | ||
| 23 | 23 | ||
| 24 | private lateinit var barcodeHandler: BarcodeHandler | 24 | private lateinit var barcodeHandler: BarcodeHandler |
| 25 | 25 | ||
| 26 | - private var permissionResult: MethodChannel.Result? = null | ||
| 27 | private var analyzerResult: MethodChannel.Result? = null | 26 | private var analyzerResult: MethodChannel.Result? = null |
| 28 | 27 | ||
| 29 | - private val permissionCallback: PermissionCallback = {hasPermission: Boolean -> | ||
| 30 | - permissionResult?.success(hasPermission) | ||
| 31 | - permissionResult = null | ||
| 32 | - } | ||
| 33 | - | ||
| 34 | private val callback: MobileScannerCallback = { barcodes: List<Map<String, Any?>>, image: ByteArray? -> | 28 | private val callback: MobileScannerCallback = { barcodes: List<Map<String, Any?>>, image: ByteArray? -> |
| 35 | if (image != null) { | 29 | if (image != null) { |
| 36 | barcodeHandler.publishEvent(mapOf( | 30 | barcodeHandler.publishEvent(mapOf( |
| @@ -78,7 +72,7 @@ class MobileScannerPlugin : FlutterPlugin, ActivityAware, MethodChannel.MethodCa | @@ -78,7 +72,7 @@ class MobileScannerPlugin : FlutterPlugin, ActivityAware, MethodChannel.MethodCa | ||
| 78 | } | 72 | } |
| 79 | when (call.method) { | 73 | when (call.method) { |
| 80 | "state" -> result.success(handler!!.hasCameraPermission()) | 74 | "state" -> result.success(handler!!.hasCameraPermission()) |
| 81 | - "request" -> requestPermission(result) | 75 | + "request" -> handler!!.requestPermission(result) |
| 82 | "start" -> start(call, result) | 76 | "start" -> start(call, result) |
| 83 | "torch" -> toggleTorch(call, result) | 77 | "torch" -> toggleTorch(call, result) |
| 84 | "stop" -> stop(result) | 78 | "stop" -> stop(result) |
| @@ -124,11 +118,6 @@ class MobileScannerPlugin : FlutterPlugin, ActivityAware, MethodChannel.MethodCa | @@ -124,11 +118,6 @@ class MobileScannerPlugin : FlutterPlugin, ActivityAware, MethodChannel.MethodCa | ||
| 124 | onDetachedFromActivity() | 118 | onDetachedFromActivity() |
| 125 | } | 119 | } |
| 126 | 120 | ||
| 127 | - private fun requestPermission(result: MethodChannel.Result) { | ||
| 128 | - permissionResult = result | ||
| 129 | - handler!!.requestPermission(permissionCallback) | ||
| 130 | - } | ||
| 131 | - | ||
| 132 | @ExperimentalGetImage | 121 | @ExperimentalGetImage |
| 133 | private fun start(call: MethodCall, result: MethodChannel.Result) { | 122 | private fun start(call: MethodCall, result: MethodChannel.Result) { |
| 134 | val torch: Boolean = call.argument<Boolean>("torch") ?: false | 123 | val torch: Boolean = call.argument<Boolean>("torch") ?: false |
| @@ -208,7 +197,7 @@ class MobileScannerPlugin : FlutterPlugin, ActivityAware, MethodChannel.MethodCa | @@ -208,7 +197,7 @@ class MobileScannerPlugin : FlutterPlugin, ActivityAware, MethodChannel.MethodCa | ||
| 208 | handler!!.stop() | 197 | handler!!.stop() |
| 209 | result.success(null) | 198 | result.success(null) |
| 210 | } catch (e: AlreadyStopped) { | 199 | } catch (e: AlreadyStopped) { |
| 211 | - result.error("MobileScanner", "Called stop() while already stopped!", null) | 200 | + result.success(null) |
| 212 | } | 201 | } |
| 213 | } | 202 | } |
| 214 | 203 |
| @@ -15,13 +15,10 @@ class _BarcodeListScannerWithControllerState | @@ -15,13 +15,10 @@ class _BarcodeListScannerWithControllerState | ||
| 15 | with SingleTickerProviderStateMixin { | 15 | with SingleTickerProviderStateMixin { |
| 16 | BarcodeCapture? barcodeCapture; | 16 | BarcodeCapture? barcodeCapture; |
| 17 | 17 | ||
| 18 | - MobileScannerController controller = MobileScannerController( | 18 | + final MobileScannerController controller = MobileScannerController( |
| 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 | 22 | // detectionSpeed: DetectionSpeed.normal |
| 26 | // detectionTimeoutMs: 1000, | 23 | // detectionTimeoutMs: 1000, |
| 27 | // returnImage: false, | 24 | // returnImage: false, |
| @@ -29,6 +26,31 @@ class _BarcodeListScannerWithControllerState | @@ -29,6 +26,31 @@ class _BarcodeListScannerWithControllerState | ||
| 29 | 26 | ||
| 30 | bool isStarted = true; | 27 | bool isStarted = true; |
| 31 | 28 | ||
| 29 | + void _startOrStop() { | ||
| 30 | + if (isStarted) { | ||
| 31 | + controller.stop(); | ||
| 32 | + } else { | ||
| 33 | + controller.start().catchError((error) { | ||
| 34 | + final exception = error as MobileScannerException; | ||
| 35 | + | ||
| 36 | + switch (exception.errorCode) { | ||
| 37 | + case MobileScannerErrorCode.controllerUninitialized: | ||
| 38 | + break; // This error code is not used by `start()`. | ||
| 39 | + case MobileScannerErrorCode.genericError: | ||
| 40 | + debugPrint('Scanner failed to start'); | ||
| 41 | + break; | ||
| 42 | + case MobileScannerErrorCode.permissionDenied: | ||
| 43 | + debugPrint('Camera permission denied'); | ||
| 44 | + break; | ||
| 45 | + } | ||
| 46 | + }); | ||
| 47 | + } | ||
| 48 | + | ||
| 49 | + setState(() { | ||
| 50 | + isStarted = !isStarted; | ||
| 51 | + }); | ||
| 52 | + } | ||
| 53 | + | ||
| 32 | @override | 54 | @override |
| 33 | Widget build(BuildContext context) { | 55 | Widget build(BuildContext context) { |
| 34 | return Scaffold( | 56 | return Scaffold( |
| @@ -40,17 +62,13 @@ class _BarcodeListScannerWithControllerState | @@ -40,17 +62,13 @@ class _BarcodeListScannerWithControllerState | ||
| 40 | MobileScanner( | 62 | MobileScanner( |
| 41 | controller: controller, | 63 | controller: controller, |
| 42 | fit: BoxFit.contain, | 64 | fit: BoxFit.contain, |
| 43 | - // controller: MobileScannerController( | ||
| 44 | - // torchEnabled: true, | ||
| 45 | - // facing: CameraFacing.front, | ||
| 46 | - // ), | ||
| 47 | onDetect: (barcodeCapture) { | 65 | onDetect: (barcodeCapture) { |
| 48 | setState(() { | 66 | setState(() { |
| 49 | this.barcodeCapture = barcodeCapture; | 67 | this.barcodeCapture = barcodeCapture; |
| 50 | }); | 68 | }); |
| 51 | }, | 69 | }, |
| 52 | - onStart: (arguments) { | ||
| 53 | - // Do something with start arguments | 70 | + onScannerStarted: (arguments) { |
| 71 | + // Do something with arguments. | ||
| 54 | }, | 72 | }, |
| 55 | ), | 73 | ), |
| 56 | Align( | 74 | Align( |
| @@ -96,10 +114,7 @@ class _BarcodeListScannerWithControllerState | @@ -96,10 +114,7 @@ class _BarcodeListScannerWithControllerState | ||
| 96 | ? const Icon(Icons.stop) | 114 | ? const Icon(Icons.stop) |
| 97 | : const Icon(Icons.play_arrow), | 115 | : const Icon(Icons.play_arrow), |
| 98 | iconSize: 32.0, | 116 | iconSize: 32.0, |
| 99 | - onPressed: () => setState(() { | ||
| 100 | - isStarted ? controller.stop() : controller.start(); | ||
| 101 | - isStarted = !isStarted; | ||
| 102 | - }), | 117 | + onPressed: _startOrStop, |
| 103 | ), | 118 | ), |
| 104 | Center( | 119 | Center( |
| 105 | child: SizedBox( | 120 | child: SizedBox( |
| @@ -177,4 +192,10 @@ class _BarcodeListScannerWithControllerState | @@ -177,4 +192,10 @@ class _BarcodeListScannerWithControllerState | ||
| 177 | ), | 192 | ), |
| 178 | ); | 193 | ); |
| 179 | } | 194 | } |
| 195 | + | ||
| 196 | + @override | ||
| 197 | + void dispose() { | ||
| 198 | + controller.dispose(); | ||
| 199 | + super.dispose(); | ||
| 200 | + } | ||
| 180 | } | 201 | } |
| @@ -15,13 +15,10 @@ class _BarcodeScannerWithControllerState | @@ -15,13 +15,10 @@ class _BarcodeScannerWithControllerState | ||
| 15 | with SingleTickerProviderStateMixin { | 15 | with SingleTickerProviderStateMixin { |
| 16 | BarcodeCapture? barcode; | 16 | BarcodeCapture? barcode; |
| 17 | 17 | ||
| 18 | - MobileScannerController controller = MobileScannerController( | 18 | + final MobileScannerController controller = MobileScannerController( |
| 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 | 22 | // detectionSpeed: DetectionSpeed.normal |
| 26 | // detectionTimeoutMs: 1000, | 23 | // detectionTimeoutMs: 1000, |
| 27 | // returnImage: false, | 24 | // returnImage: false, |
| @@ -29,6 +26,31 @@ class _BarcodeScannerWithControllerState | @@ -29,6 +26,31 @@ class _BarcodeScannerWithControllerState | ||
| 29 | 26 | ||
| 30 | bool isStarted = true; | 27 | bool isStarted = true; |
| 31 | 28 | ||
| 29 | + void _startOrStop() { | ||
| 30 | + if (isStarted) { | ||
| 31 | + controller.stop(); | ||
| 32 | + } else { | ||
| 33 | + controller.start().catchError((error) { | ||
| 34 | + final exception = error as MobileScannerException; | ||
| 35 | + | ||
| 36 | + switch (exception.errorCode) { | ||
| 37 | + case MobileScannerErrorCode.controllerUninitialized: | ||
| 38 | + break; // This error code is not used by `start()`. | ||
| 39 | + case MobileScannerErrorCode.genericError: | ||
| 40 | + debugPrint('Scanner failed to start'); | ||
| 41 | + break; | ||
| 42 | + case MobileScannerErrorCode.permissionDenied: | ||
| 43 | + debugPrint('Camera permission denied'); | ||
| 44 | + break; | ||
| 45 | + } | ||
| 46 | + }); | ||
| 47 | + } | ||
| 48 | + | ||
| 49 | + setState(() { | ||
| 50 | + isStarted = !isStarted; | ||
| 51 | + }); | ||
| 52 | + } | ||
| 53 | + | ||
| 32 | @override | 54 | @override |
| 33 | Widget build(BuildContext context) { | 55 | Widget build(BuildContext context) { |
| 34 | return Scaffold( | 56 | return Scaffold( |
| @@ -40,10 +62,6 @@ class _BarcodeScannerWithControllerState | @@ -40,10 +62,6 @@ class _BarcodeScannerWithControllerState | ||
| 40 | MobileScanner( | 62 | MobileScanner( |
| 41 | controller: controller, | 63 | controller: controller, |
| 42 | fit: BoxFit.contain, | 64 | fit: BoxFit.contain, |
| 43 | - // controller: MobileScannerController( | ||
| 44 | - // torchEnabled: true, | ||
| 45 | - // facing: CameraFacing.front, | ||
| 46 | - // ), | ||
| 47 | onDetect: (barcode) { | 65 | onDetect: (barcode) { |
| 48 | setState(() { | 66 | setState(() { |
| 49 | this.barcode = barcode; | 67 | this.barcode = barcode; |
| @@ -93,10 +111,7 @@ class _BarcodeScannerWithControllerState | @@ -93,10 +111,7 @@ class _BarcodeScannerWithControllerState | ||
| 93 | ? const Icon(Icons.stop) | 111 | ? const Icon(Icons.stop) |
| 94 | : const Icon(Icons.play_arrow), | 112 | : const Icon(Icons.play_arrow), |
| 95 | iconSize: 32.0, | 113 | iconSize: 32.0, |
| 96 | - onPressed: () => setState(() { | ||
| 97 | - isStarted ? controller.stop() : controller.start(); | ||
| 98 | - isStarted = !isStarted; | ||
| 99 | - }), | 114 | + onPressed: _startOrStop, |
| 100 | ), | 115 | ), |
| 101 | Center( | 116 | Center( |
| 102 | child: SizedBox( | 117 | child: SizedBox( |
| @@ -175,4 +190,10 @@ class _BarcodeScannerWithControllerState | @@ -175,4 +190,10 @@ class _BarcodeScannerWithControllerState | ||
| 175 | ), | 190 | ), |
| 176 | ); | 191 | ); |
| 177 | } | 192 | } |
| 193 | + | ||
| 194 | + @override | ||
| 195 | + void dispose() { | ||
| 196 | + controller.dispose(); | ||
| 197 | + super.dispose(); | ||
| 198 | + } | ||
| 178 | } | 199 | } |
| @@ -17,13 +17,10 @@ class _BarcodeScannerReturningImageState | @@ -17,13 +17,10 @@ class _BarcodeScannerReturningImageState | ||
| 17 | BarcodeCapture? barcode; | 17 | BarcodeCapture? barcode; |
| 18 | MobileScannerArguments? arguments; | 18 | MobileScannerArguments? arguments; |
| 19 | 19 | ||
| 20 | - MobileScannerController controller = MobileScannerController( | 20 | + final MobileScannerController controller = MobileScannerController( |
| 21 | torchEnabled: true, | 21 | torchEnabled: true, |
| 22 | // formats: [BarcodeFormat.qrCode] | 22 | // formats: [BarcodeFormat.qrCode] |
| 23 | // facing: CameraFacing.front, | 23 | // facing: CameraFacing.front, |
| 24 | - onPermissionSet: (hasPermission) { | ||
| 25 | - // Do something with permission callback | ||
| 26 | - }, | ||
| 27 | // detectionSpeed: DetectionSpeed.normal | 24 | // detectionSpeed: DetectionSpeed.normal |
| 28 | // detectionTimeoutMs: 1000, | 25 | // detectionTimeoutMs: 1000, |
| 29 | returnImage: true, | 26 | returnImage: true, |
| @@ -31,6 +28,31 @@ class _BarcodeScannerReturningImageState | @@ -31,6 +28,31 @@ class _BarcodeScannerReturningImageState | ||
| 31 | 28 | ||
| 32 | bool isStarted = true; | 29 | bool isStarted = true; |
| 33 | 30 | ||
| 31 | + void _startOrStop() { | ||
| 32 | + if (isStarted) { | ||
| 33 | + controller.stop(); | ||
| 34 | + } else { | ||
| 35 | + controller.start().catchError((error) { | ||
| 36 | + final exception = error as MobileScannerException; | ||
| 37 | + | ||
| 38 | + switch (exception.errorCode) { | ||
| 39 | + case MobileScannerErrorCode.controllerUninitialized: | ||
| 40 | + break; // This error code is not used by `start()`. | ||
| 41 | + case MobileScannerErrorCode.genericError: | ||
| 42 | + debugPrint('Scanner failed to start'); | ||
| 43 | + break; | ||
| 44 | + case MobileScannerErrorCode.permissionDenied: | ||
| 45 | + debugPrint('Camera permission denied'); | ||
| 46 | + break; | ||
| 47 | + } | ||
| 48 | + }); | ||
| 49 | + } | ||
| 50 | + | ||
| 51 | + setState(() { | ||
| 52 | + isStarted = !isStarted; | ||
| 53 | + }); | ||
| 54 | + } | ||
| 55 | + | ||
| 34 | @override | 56 | @override |
| 35 | Widget build(BuildContext context) { | 57 | Widget build(BuildContext context) { |
| 36 | return Scaffold( | 58 | return Scaffold( |
| @@ -62,10 +84,6 @@ class _BarcodeScannerReturningImageState | @@ -62,10 +84,6 @@ class _BarcodeScannerReturningImageState | ||
| 62 | MobileScanner( | 84 | MobileScanner( |
| 63 | controller: controller, | 85 | controller: controller, |
| 64 | fit: BoxFit.contain, | 86 | fit: BoxFit.contain, |
| 65 | - // controller: MobileScannerController( | ||
| 66 | - // torchEnabled: true, | ||
| 67 | - // facing: CameraFacing.front, | ||
| 68 | - // ), | ||
| 69 | onDetect: (barcode) { | 87 | onDetect: (barcode) { |
| 70 | setState(() { | 88 | setState(() { |
| 71 | this.barcode = barcode; | 89 | this.barcode = barcode; |
| @@ -115,12 +133,7 @@ class _BarcodeScannerReturningImageState | @@ -115,12 +133,7 @@ class _BarcodeScannerReturningImageState | ||
| 115 | ? const Icon(Icons.stop) | 133 | ? const Icon(Icons.stop) |
| 116 | : const Icon(Icons.play_arrow), | 134 | : const Icon(Icons.play_arrow), |
| 117 | iconSize: 32.0, | 135 | iconSize: 32.0, |
| 118 | - onPressed: () => setState(() { | ||
| 119 | - isStarted | ||
| 120 | - ? controller.stop() | ||
| 121 | - : controller.start(); | ||
| 122 | - isStarted = !isStarted; | ||
| 123 | - }), | 136 | + onPressed: _startOrStop, |
| 124 | ), | 137 | ), |
| 125 | Center( | 138 | Center( |
| 126 | child: SizedBox( | 139 | child: SizedBox( |
| @@ -171,4 +184,10 @@ class _BarcodeScannerReturningImageState | @@ -171,4 +184,10 @@ class _BarcodeScannerReturningImageState | ||
| 171 | ), | 184 | ), |
| 172 | ); | 185 | ); |
| 173 | } | 186 | } |
| 187 | + | ||
| 188 | + @override | ||
| 189 | + void dispose() { | ||
| 190 | + controller.dispose(); | ||
| 191 | + super.dispose(); | ||
| 192 | + } | ||
| 174 | } | 193 | } |
| @@ -107,11 +107,7 @@ public class SwiftMobileScannerPlugin: NSObject, FlutterPlugin { | @@ -107,11 +107,7 @@ public class SwiftMobileScannerPlugin: NSObject, FlutterPlugin { | ||
| 107 | private func stop(_ result: @escaping FlutterResult) { | 107 | private func stop(_ result: @escaping FlutterResult) { |
| 108 | do { | 108 | do { |
| 109 | try mobileScanner.stop() | 109 | try mobileScanner.stop() |
| 110 | - } catch { | ||
| 111 | - result(FlutterError(code: "MobileScanner", | ||
| 112 | - message: "Called stop() while already stopped!", | ||
| 113 | - details: nil)) | ||
| 114 | - } | 110 | + } catch {} |
| 115 | result(nil) | 111 | result(nil) |
| 116 | } | 112 | } |
| 117 | 113 |
| @@ -2,11 +2,13 @@ library mobile_scanner; | @@ -2,11 +2,13 @@ library mobile_scanner; | ||
| 2 | 2 | ||
| 3 | export 'src/enums/camera_facing.dart'; | 3 | export 'src/enums/camera_facing.dart'; |
| 4 | export 'src/enums/detection_speed.dart'; | 4 | export 'src/enums/detection_speed.dart'; |
| 5 | +export 'src/enums/mobile_scanner_error_code.dart'; | ||
| 5 | export 'src/enums/mobile_scanner_state.dart'; | 6 | export 'src/enums/mobile_scanner_state.dart'; |
| 6 | export 'src/enums/ratio.dart'; | 7 | export 'src/enums/ratio.dart'; |
| 7 | export 'src/enums/torch_state.dart'; | 8 | export 'src/enums/torch_state.dart'; |
| 8 | export 'src/mobile_scanner.dart'; | 9 | export 'src/mobile_scanner.dart'; |
| 9 | export 'src/mobile_scanner_controller.dart'; | 10 | export 'src/mobile_scanner_controller.dart'; |
| 11 | +export 'src/mobile_scanner_exception.dart'; | ||
| 10 | export 'src/objects/barcode.dart'; | 12 | export 'src/objects/barcode.dart'; |
| 11 | export 'src/objects/barcode_capture.dart'; | 13 | export 'src/objects/barcode_capture.dart'; |
| 12 | export 'src/objects/mobile_scanner_arguments.dart'; | 14 | export 'src/objects/mobile_scanner_arguments.dart'; |
lib/src/enums/mobile_scanner_error_code.dart
0 → 100644
| 1 | +/// This enum defines the different error codes for the mobile scanner. | ||
| 2 | +enum MobileScannerErrorCode { | ||
| 3 | + /// The controller was used | ||
| 4 | + /// while it was not yet initialized using [MobileScannerController.start]. | ||
| 5 | + controllerUninitialized, | ||
| 6 | + | ||
| 7 | + /// A generic error occurred. | ||
| 8 | + /// | ||
| 9 | + /// This error code is used for all errors that do not have a specific error code. | ||
| 10 | + genericError, | ||
| 11 | + | ||
| 12 | + /// The permission to use the camera was denied. | ||
| 13 | + permissionDenied, | ||
| 14 | +} |
| 1 | /// The authorization state of the scanner. | 1 | /// The authorization state of the scanner. |
| 2 | enum MobileScannerState { | 2 | enum MobileScannerState { |
| 3 | - /// The scanner has yet to request weather it is [authorized] or [denied] | 3 | + /// The scanner has not yet requested the required permissions. |
| 4 | undetermined, | 4 | undetermined, |
| 5 | 5 | ||
| 6 | /// The scanner has the required permissions. | 6 | /// The scanner has the required permissions. |
| 1 | +import 'dart:async'; | ||
| 2 | + | ||
| 1 | import 'package:flutter/foundation.dart'; | 3 | import 'package:flutter/foundation.dart'; |
| 2 | import 'package:flutter/material.dart'; | 4 | import 'package:flutter/material.dart'; |
| 3 | import 'package:mobile_scanner/src/mobile_scanner_controller.dart'; | 5 | import 'package:mobile_scanner/src/mobile_scanner_controller.dart'; |
| 4 | import 'package:mobile_scanner/src/objects/barcode_capture.dart'; | 6 | import 'package:mobile_scanner/src/objects/barcode_capture.dart'; |
| 5 | import 'package:mobile_scanner/src/objects/mobile_scanner_arguments.dart'; | 7 | import 'package:mobile_scanner/src/objects/mobile_scanner_arguments.dart'; |
| 6 | 8 | ||
| 7 | -typedef MobileScannerCallback = void Function(BarcodeCapture barcodes); | ||
| 8 | -typedef MobileScannerArgumentsCallback = void Function( | ||
| 9 | - MobileScannerArguments? arguments, | ||
| 10 | -); | ||
| 11 | - | ||
| 12 | -/// A widget showing a live camera preview. | 9 | +/// The [MobileScanner] widget displays a live camera preview. |
| 13 | class MobileScanner extends StatefulWidget { | 10 | class MobileScanner extends StatefulWidget { |
| 14 | - /// The controller of the camera. | 11 | + /// The controller that manages the barcode scanner. |
| 12 | + /// | ||
| 13 | + /// If this is null, the scanner will manage its own controller. | ||
| 15 | final MobileScannerController? controller; | 14 | final MobileScannerController? controller; |
| 16 | 15 | ||
| 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 | ||
| 20 | - final Function(bool permissionGranted)? onPermissionSet; | ||
| 21 | - | ||
| 22 | - /// Function that gets called when a Barcode is detected. | 16 | + /// The [BoxFit] for the camera preview. |
| 23 | /// | 17 | /// |
| 24 | - /// [barcode] The barcode object with all information about the scanned code. | ||
| 25 | - /// [startInternalArguments] Information about the state of the MobileScanner widget | ||
| 26 | - final MobileScannerCallback onDetect; | 18 | + /// Defaults to [BoxFit.cover]. |
| 19 | + final BoxFit fit; | ||
| 27 | 20 | ||
| 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 | + /// The function that signals when new codes were detected by the [controller]. |
| 22 | + final void Function(BarcodeCapture barcodes) onDetect; | ||
| 33 | 23 | ||
| 34 | - /// Handles how the widget should fit the screen. | ||
| 35 | - final BoxFit fit; | 24 | + /// The function that signals when the barcode scanner is started. |
| 25 | + @Deprecated('Use onScannerStarted() instead.') | ||
| 26 | + final void Function(MobileScannerArguments? arguments)? onStart; | ||
| 27 | + | ||
| 28 | + /// The function that signals when the barcode scanner is started. | ||
| 29 | + final void Function(MobileScannerArguments? arguments)? onScannerStarted; | ||
| 36 | 30 | ||
| 37 | - /// Whether to automatically resume the camera when the application is resumed | ||
| 38 | - final bool autoResume; | 31 | + /// The function that builds a placeholder widget when the scanner |
| 32 | + /// is not yet displaying its camera preview. | ||
| 33 | + /// | ||
| 34 | + /// If this is null, a black [ColoredBox] is used as placeholder. | ||
| 35 | + final Widget Function(BuildContext, Widget?)? placeholderBuilder; | ||
| 39 | 36 | ||
| 40 | - /// Create a [MobileScanner] with a [controller], the [controller] must has been initialized. | 37 | + /// Create a new [MobileScanner] using the provided [controller] |
| 38 | + /// and [onBarcodeDetected] callback. | ||
| 41 | const MobileScanner({ | 39 | const MobileScanner({ |
| 42 | - super.key, | ||
| 43 | - required this.onDetect, | ||
| 44 | - this.onStart, | ||
| 45 | this.controller, | 40 | this.controller, |
| 46 | - this.autoResume = true, | ||
| 47 | this.fit = BoxFit.cover, | 41 | this.fit = BoxFit.cover, |
| 48 | - @Deprecated('Use the [onPermissionSet] paremeter in the [MobileScannerController] instead.') | ||
| 49 | - this.onPermissionSet, | 42 | + required this.onDetect, |
| 43 | + @Deprecated('Use onScannerStarted() instead.') this.onStart, | ||
| 44 | + this.onScannerStarted, | ||
| 45 | + this.placeholderBuilder, | ||
| 46 | + super.key, | ||
| 50 | }); | 47 | }); |
| 51 | 48 | ||
| 52 | @override | 49 | @override |
| @@ -55,65 +52,86 @@ class MobileScanner extends StatefulWidget { | @@ -55,65 +52,86 @@ class MobileScanner extends StatefulWidget { | ||
| 55 | 52 | ||
| 56 | class _MobileScannerState extends State<MobileScanner> | 53 | class _MobileScannerState extends State<MobileScanner> |
| 57 | with WidgetsBindingObserver { | 54 | with WidgetsBindingObserver { |
| 58 | - late MobileScannerController controller; | 55 | + /// The subscription that listens to barcode detection. |
| 56 | + StreamSubscription<BarcodeCapture>? _barcodesSubscription; | ||
| 57 | + | ||
| 58 | + /// The internally managed controller. | ||
| 59 | + late MobileScannerController _controller; | ||
| 60 | + | ||
| 61 | + /// Whether the controller should resume | ||
| 62 | + /// when the application comes back to the foreground. | ||
| 63 | + bool _resumeFromBackground = false; | ||
| 64 | + | ||
| 65 | + /// Start the given [scanner]. | ||
| 66 | + void _startScanner(MobileScannerController scanner) { | ||
| 67 | + if (!_controller.autoStart) { | ||
| 68 | + debugPrint( | ||
| 69 | + 'mobile_scanner: not starting automatically because autoStart is set to false in the controller.', | ||
| 70 | + ); | ||
| 71 | + return; | ||
| 72 | + } | ||
| 73 | + scanner.start().then((arguments) { | ||
| 74 | + // ignore: deprecated_member_use_from_same_package | ||
| 75 | + widget.onStart?.call(arguments); | ||
| 76 | + widget.onScannerStarted?.call(arguments); | ||
| 77 | + }); | ||
| 78 | + } | ||
| 59 | 79 | ||
| 60 | @override | 80 | @override |
| 61 | void initState() { | 81 | void initState() { |
| 62 | super.initState(); | 82 | super.initState(); |
| 63 | WidgetsBinding.instance.addObserver(this); | 83 | WidgetsBinding.instance.addObserver(this); |
| 64 | - controller = widget.controller ?? | ||
| 65 | - MobileScannerController(onPermissionSet: widget.onPermissionSet); | ||
| 66 | - if (!controller.isStarting) { | ||
| 67 | - _startScanner(); | ||
| 68 | - } | ||
| 69 | - } | 84 | + _controller = widget.controller ?? MobileScannerController(); |
| 70 | 85 | ||
| 71 | - Future<void> _startScanner() async { | ||
| 72 | - final arguments = await controller.start(); | ||
| 73 | - widget.onStart?.call(arguments); | ||
| 74 | - } | 86 | + _barcodesSubscription = _controller.barcodes.listen( |
| 87 | + widget.onDetect, | ||
| 88 | + ); | ||
| 75 | 89 | ||
| 76 | - bool resumeFromBackground = false; | 90 | + if (!_controller.isStarting) { |
| 91 | + _startScanner(_controller); | ||
| 92 | + } | ||
| 93 | + } | ||
| 77 | 94 | ||
| 78 | @override | 95 | @override |
| 79 | void didChangeAppLifecycleState(AppLifecycleState state) { | 96 | void didChangeAppLifecycleState(AppLifecycleState state) { |
| 80 | - // App state changed before it is initialized. | ||
| 81 | - if (controller.isStarting) { | 97 | + // App state changed before the controller was initialized. |
| 98 | + if (_controller.isStarting) { | ||
| 82 | return; | 99 | return; |
| 83 | } | 100 | } |
| 84 | 101 | ||
| 85 | switch (state) { | 102 | switch (state) { |
| 86 | case AppLifecycleState.resumed: | 103 | case AppLifecycleState.resumed: |
| 87 | - resumeFromBackground = false; | ||
| 88 | - _startScanner(); | 104 | + _resumeFromBackground = false; |
| 105 | + _startScanner(_controller); | ||
| 89 | break; | 106 | break; |
| 90 | case AppLifecycleState.paused: | 107 | case AppLifecycleState.paused: |
| 91 | - resumeFromBackground = true; | 108 | + _resumeFromBackground = true; |
| 92 | break; | 109 | break; |
| 93 | case AppLifecycleState.inactive: | 110 | case AppLifecycleState.inactive: |
| 94 | - if (!resumeFromBackground) controller.stop(); | 111 | + if (!_resumeFromBackground) { |
| 112 | + _controller.stop(); | ||
| 113 | + } | ||
| 95 | break; | 114 | break; |
| 96 | - default: | 115 | + case AppLifecycleState.detached: |
| 97 | break; | 116 | break; |
| 98 | } | 117 | } |
| 99 | } | 118 | } |
| 100 | 119 | ||
| 101 | @override | 120 | @override |
| 102 | Widget build(BuildContext context) { | 121 | Widget build(BuildContext context) { |
| 103 | - return ValueListenableBuilder( | ||
| 104 | - valueListenable: controller.startArguments, | 122 | + return ValueListenableBuilder<MobileScannerArguments?>( |
| 123 | + valueListenable: _controller.startArguments, | ||
| 105 | builder: (context, value, child) { | 124 | builder: (context, value, child) { |
| 106 | - value = value as MobileScannerArguments?; | ||
| 107 | if (value == null) { | 125 | if (value == null) { |
| 108 | - return const ColoredBox(color: Colors.black); | ||
| 109 | - } else { | ||
| 110 | - controller.barcodes.listen((barcode) { | ||
| 111 | - widget.onDetect(barcode); | ||
| 112 | - }); | 126 | + return widget.placeholderBuilder?.call(context, child) ?? |
| 127 | + const ColoredBox(color: Colors.black); | ||
| 128 | + } | ||
| 129 | + | ||
| 113 | return ClipRect( | 130 | return ClipRect( |
| 114 | - child: SizedBox( | ||
| 115 | - width: MediaQuery.of(context).size.width, | ||
| 116 | - height: MediaQuery.of(context).size.height, | 131 | + child: LayoutBuilder( |
| 132 | + builder: (_, constraints) { | ||
| 133 | + return SizedBox.fromSize( | ||
| 134 | + size: constraints.biggest, | ||
| 117 | child: FittedBox( | 135 | child: FittedBox( |
| 118 | fit: widget.fit, | 136 | fit: widget.fit, |
| 119 | child: SizedBox( | 137 | child: SizedBox( |
| @@ -124,35 +142,19 @@ class _MobileScannerState extends State<MobileScanner> | @@ -124,35 +142,19 @@ class _MobileScannerState extends State<MobileScanner> | ||
| 124 | : Texture(textureId: value.textureId!), | 142 | : Texture(textureId: value.textureId!), |
| 125 | ), | 143 | ), |
| 126 | ), | 144 | ), |
| 145 | + ); | ||
| 146 | + }, | ||
| 127 | ), | 147 | ), |
| 128 | ); | 148 | ); |
| 129 | - } | ||
| 130 | }, | 149 | }, |
| 131 | ); | 150 | ); |
| 132 | } | 151 | } |
| 133 | 152 | ||
| 134 | @override | 153 | @override |
| 135 | - void didUpdateWidget(covariant MobileScanner oldWidget) { | ||
| 136 | - super.didUpdateWidget(oldWidget); | ||
| 137 | - if (oldWidget.controller == null) { | ||
| 138 | - if (widget.controller != null) { | ||
| 139 | - controller.dispose(); | ||
| 140 | - controller = widget.controller!; | ||
| 141 | - } | ||
| 142 | - } else { | ||
| 143 | - if (widget.controller == null) { | ||
| 144 | - controller = | ||
| 145 | - MobileScannerController(onPermissionSet: widget.onPermissionSet); | ||
| 146 | - } else if (oldWidget.controller != widget.controller) { | ||
| 147 | - controller = widget.controller!; | ||
| 148 | - } | ||
| 149 | - } | ||
| 150 | - } | ||
| 151 | - | ||
| 152 | - @override | ||
| 153 | void dispose() { | 154 | void dispose() { |
| 154 | - controller.dispose(); | ||
| 155 | WidgetsBinding.instance.removeObserver(this); | 155 | WidgetsBinding.instance.removeObserver(this); |
| 156 | + _barcodesSubscription?.cancel(); | ||
| 157 | + _controller.dispose(); | ||
| 156 | super.dispose(); | 158 | super.dispose(); |
| 157 | } | 159 | } |
| 158 | } | 160 | } |
| @@ -6,7 +6,6 @@ import 'package:flutter/foundation.dart'; | @@ -6,7 +6,6 @@ import 'package:flutter/foundation.dart'; | ||
| 6 | import 'package:flutter/services.dart'; | 6 | import 'package:flutter/services.dart'; |
| 7 | import 'package:mobile_scanner/mobile_scanner.dart'; | 7 | import 'package:mobile_scanner/mobile_scanner.dart'; |
| 8 | import 'package:mobile_scanner/src/barcode_utility.dart'; | 8 | import 'package:mobile_scanner/src/barcode_utility.dart'; |
| 9 | -import 'package:mobile_scanner/src/mobile_scanner_exception.dart'; | ||
| 10 | 9 | ||
| 11 | /// The [MobileScannerController] holds all the logic of this plugin, | 10 | /// The [MobileScannerController] holds all the logic of this plugin, |
| 12 | /// where as the [MobileScanner] class is the frontend of this plugin. | 11 | /// where as the [MobileScanner] class is the frontend of this plugin. |
| @@ -18,7 +17,9 @@ class MobileScannerController { | @@ -18,7 +17,9 @@ class MobileScannerController { | ||
| 18 | this.torchEnabled = false, | 17 | this.torchEnabled = false, |
| 19 | this.formats, | 18 | this.formats, |
| 20 | this.returnImage = false, | 19 | this.returnImage = false, |
| 20 | + @Deprecated('Instead, use the result of calling `start()` to determine if permissions were granted.') | ||
| 21 | this.onPermissionSet, | 21 | this.onPermissionSet, |
| 22 | + this.autoStart = true, | ||
| 22 | }) { | 23 | }) { |
| 23 | // In case a new instance is created before calling dispose() | 24 | // In case a new instance is created before calling dispose() |
| 24 | if (controllerHashcode != null) { | 25 | if (controllerHashcode != null) { |
| @@ -64,6 +65,9 @@ class MobileScannerController { | @@ -64,6 +65,9 @@ class MobileScannerController { | ||
| 64 | /// [DetectionSpeed.normal] (which is the default value). | 65 | /// [DetectionSpeed.normal] (which is the default value). |
| 65 | final int detectionTimeoutMs; | 66 | final int detectionTimeoutMs; |
| 66 | 67 | ||
| 68 | + /// Automatically start the mobileScanner on initialization. | ||
| 69 | + final bool autoStart; | ||
| 70 | + | ||
| 67 | /// Sets the barcode stream | 71 | /// Sets the barcode stream |
| 68 | final StreamController<BarcodeCapture> _barcodesController = | 72 | final StreamController<BarcodeCapture> _barcodesController = |
| 69 | StreamController.broadcast(); | 73 | StreamController.broadcast(); |
| @@ -74,6 +78,9 @@ class MobileScannerController { | @@ -74,6 +78,9 @@ class MobileScannerController { | ||
| 74 | static const EventChannel _eventChannel = | 78 | static const EventChannel _eventChannel = |
| 75 | EventChannel('dev.steenbakker.mobile_scanner/scanner/event'); | 79 | EventChannel('dev.steenbakker.mobile_scanner/scanner/event'); |
| 76 | 80 | ||
| 81 | + @Deprecated( | ||
| 82 | + 'Instead, use the result of calling `start()` to determine if permissions were granted.', | ||
| 83 | + ) | ||
| 77 | Function(bool permissionGranted)? onPermissionSet; | 84 | Function(bool permissionGranted)? onPermissionSet; |
| 78 | 85 | ||
| 79 | /// Listen to events from the platform specific code | 86 | /// Listen to events from the platform specific code |
| @@ -94,6 +101,19 @@ class MobileScannerController { | @@ -94,6 +101,19 @@ class MobileScannerController { | ||
| 94 | 101 | ||
| 95 | bool? _hasTorch; | 102 | bool? _hasTorch; |
| 96 | 103 | ||
| 104 | + /// Returns whether the device has a torch. | ||
| 105 | + /// | ||
| 106 | + /// Throws an error if the controller is not initialized. | ||
| 107 | + bool get hasTorch { | ||
| 108 | + if (_hasTorch == null) { | ||
| 109 | + throw const MobileScannerException( | ||
| 110 | + errorCode: MobileScannerErrorCode.controllerUninitialized, | ||
| 111 | + ); | ||
| 112 | + } | ||
| 113 | + | ||
| 114 | + return _hasTorch!; | ||
| 115 | + } | ||
| 116 | + | ||
| 97 | /// Set the starting arguments for the camera | 117 | /// Set the starting arguments for the camera |
| 98 | Map<String, dynamic> _argumentsToMap({CameraFacing? cameraFacingOverride}) { | 118 | Map<String, dynamic> _argumentsToMap({CameraFacing? cameraFacingOverride}) { |
| 99 | final Map<String, dynamic> arguments = {}; | 119 | final Map<String, dynamic> arguments = {}; |
| @@ -115,8 +135,14 @@ class MobileScannerController { | @@ -115,8 +135,14 @@ class MobileScannerController { | ||
| 115 | return arguments; | 135 | return arguments; |
| 116 | } | 136 | } |
| 117 | 137 | ||
| 118 | - /// Start barcode scanning. This will first check if the required permissions | ||
| 119 | - /// are set. | 138 | + /// Start scanning for barcodes. |
| 139 | + /// Upon calling this method, the necessary camera permission will be requested. | ||
| 140 | + /// | ||
| 141 | + /// Returns an instance of [MobileScannerArguments] | ||
| 142 | + /// when the scanner was successfully started. | ||
| 143 | + /// Returns null if the scanner is currently starting. | ||
| 144 | + /// | ||
| 145 | + /// Throws a [MobileScannerException] if starting the scanner failed. | ||
| 120 | Future<MobileScannerArguments?> start({ | 146 | Future<MobileScannerArguments?> start({ |
| 121 | CameraFacing? cameraFacingOverride, | 147 | CameraFacing? cameraFacingOverride, |
| 122 | }) async { | 148 | }) async { |
| @@ -124,6 +150,7 @@ class MobileScannerController { | @@ -124,6 +150,7 @@ class MobileScannerController { | ||
| 124 | debugPrint("Called start() while starting."); | 150 | debugPrint("Called start() while starting."); |
| 125 | return null; | 151 | return null; |
| 126 | } | 152 | } |
| 153 | + | ||
| 127 | isStarting = true; | 154 | isStarting = true; |
| 128 | 155 | ||
| 129 | // Check authorization status | 156 | // Check authorization status |
| @@ -136,16 +163,17 @@ class MobileScannerController { | @@ -136,16 +163,17 @@ class MobileScannerController { | ||
| 136 | await _methodChannel.invokeMethod('request') as bool? ?? false; | 163 | await _methodChannel.invokeMethod('request') as bool? ?? false; |
| 137 | if (!result) { | 164 | if (!result) { |
| 138 | isStarting = false; | 165 | isStarting = false; |
| 139 | - onPermissionSet?.call(result); | ||
| 140 | - throw MobileScannerException('User declined camera permission.'); | 166 | + throw const MobileScannerException( |
| 167 | + errorCode: MobileScannerErrorCode.permissionDenied, | ||
| 168 | + ); | ||
| 141 | } | 169 | } |
| 142 | break; | 170 | break; |
| 143 | case MobileScannerState.denied: | 171 | case MobileScannerState.denied: |
| 144 | isStarting = false; | 172 | isStarting = false; |
| 145 | - onPermissionSet?.call(false); | ||
| 146 | - throw MobileScannerException('User declined camera permission.'); | 173 | + throw const MobileScannerException( |
| 174 | + errorCode: MobileScannerErrorCode.permissionDenied, | ||
| 175 | + ); | ||
| 147 | case MobileScannerState.authorized: | 176 | case MobileScannerState.authorized: |
| 148 | - onPermissionSet?.call(true); | ||
| 149 | break; | 177 | break; |
| 150 | } | 178 | } |
| 151 | } | 179 | } |
| @@ -158,18 +186,27 @@ class MobileScannerController { | @@ -158,18 +186,27 @@ class MobileScannerController { | ||
| 158 | _argumentsToMap(cameraFacingOverride: cameraFacingOverride), | 186 | _argumentsToMap(cameraFacingOverride: cameraFacingOverride), |
| 159 | ); | 187 | ); |
| 160 | } on PlatformException catch (error) { | 188 | } on PlatformException catch (error) { |
| 161 | - debugPrint('${error.code}: ${error.message}'); | 189 | + MobileScannerErrorCode errorCode = MobileScannerErrorCode.genericError; |
| 190 | + | ||
| 162 | if (error.code == "MobileScannerWeb") { | 191 | if (error.code == "MobileScannerWeb") { |
| 163 | - onPermissionSet?.call(false); | 192 | + errorCode = MobileScannerErrorCode.permissionDenied; |
| 164 | } | 193 | } |
| 165 | isStarting = false; | 194 | isStarting = false; |
| 166 | - return null; | 195 | + |
| 196 | + throw MobileScannerException( | ||
| 197 | + errorCode: errorCode, | ||
| 198 | + errorDetails: MobileScannerErrorDetails( | ||
| 199 | + code: error.code, | ||
| 200 | + details: error.details as Object?, | ||
| 201 | + message: error.message, | ||
| 202 | + ), | ||
| 203 | + ); | ||
| 167 | } | 204 | } |
| 168 | 205 | ||
| 169 | if (startResult == null) { | 206 | if (startResult == null) { |
| 170 | isStarting = false; | 207 | isStarting = false; |
| 171 | - throw MobileScannerException( | ||
| 172 | - 'Failed to start mobileScanner, no response from platform side', | 208 | + throw const MobileScannerException( |
| 209 | + errorCode: MobileScannerErrorCode.genericError, | ||
| 173 | ); | 210 | ); |
| 174 | } | 211 | } |
| 175 | 212 | ||
| @@ -178,13 +215,6 @@ class MobileScannerController { | @@ -178,13 +215,6 @@ class MobileScannerController { | ||
| 178 | torchState.value = TorchState.on; | 215 | torchState.value = TorchState.on; |
| 179 | } | 216 | } |
| 180 | 217 | ||
| 181 | - if (kIsWeb) { | ||
| 182 | - // If we reach this line, it means camera permission has been granted | ||
| 183 | - onPermissionSet?.call( | ||
| 184 | - true, | ||
| 185 | - ); | ||
| 186 | - } | ||
| 187 | - | ||
| 188 | isStarting = false; | 218 | isStarting = false; |
| 189 | return startArguments.value = MobileScannerArguments( | 219 | return startArguments.value = MobileScannerArguments( |
| 190 | size: kIsWeb | 220 | size: kIsWeb |
| @@ -210,14 +240,18 @@ class MobileScannerController { | @@ -210,14 +240,18 @@ class MobileScannerController { | ||
| 210 | 240 | ||
| 211 | /// Switches the torch on or off. | 241 | /// Switches the torch on or off. |
| 212 | /// | 242 | /// |
| 213 | - /// Only works if torch is available. | 243 | + /// Does nothing if the device has no torch. |
| 244 | + /// | ||
| 245 | + /// Throws if the controller was not initialized. | ||
| 214 | Future<void> toggleTorch() async { | 246 | Future<void> toggleTorch() async { |
| 215 | - if (_hasTorch == null) { | ||
| 216 | - throw MobileScannerException( | ||
| 217 | - 'Cannot toggle torch if start() has never been called', | 247 | + final hasTorch = _hasTorch; |
| 248 | + | ||
| 249 | + if (hasTorch == null) { | ||
| 250 | + throw const MobileScannerException( | ||
| 251 | + errorCode: MobileScannerErrorCode.controllerUninitialized, | ||
| 218 | ); | 252 | ); |
| 219 | - } else if (!_hasTorch!) { | ||
| 220 | - throw MobileScannerException('Device has no torch'); | 253 | + } else if (!hasTorch) { |
| 254 | + return; | ||
| 221 | } | 255 | } |
| 222 | 256 | ||
| 223 | torchState.value = | 257 | torchState.value = |
| @@ -258,7 +292,6 @@ class MobileScannerController { | @@ -258,7 +292,6 @@ class MobileScannerController { | ||
| 258 | _barcodesController.close(); | 292 | _barcodesController.close(); |
| 259 | if (hashCode == controllerHashcode) { | 293 | if (hashCode == controllerHashcode) { |
| 260 | controllerHashcode = null; | 294 | controllerHashcode = null; |
| 261 | - onPermissionSet = null; | ||
| 262 | } | 295 | } |
| 263 | } | 296 | } |
| 264 | 297 | ||
| @@ -307,7 +340,10 @@ class MobileScannerController { | @@ -307,7 +340,10 @@ class MobileScannerController { | ||
| 307 | ); | 340 | ); |
| 308 | break; | 341 | break; |
| 309 | case 'error': | 342 | case 'error': |
| 310 | - throw MobileScannerException(data as String); | 343 | + throw MobileScannerException( |
| 344 | + errorCode: MobileScannerErrorCode.genericError, | ||
| 345 | + errorDetails: MobileScannerErrorDetails(message: data as String?), | ||
| 346 | + ); | ||
| 311 | default: | 347 | default: |
| 312 | throw UnimplementedError(name as String?); | 348 | throw UnimplementedError(name as String?); |
| 313 | } | 349 | } |
| 1 | +import 'package:mobile_scanner/src/enums/mobile_scanner_error_code.dart'; | ||
| 2 | + | ||
| 3 | +/// This class represents an exception thrown by the mobile scanner. | ||
| 1 | class MobileScannerException implements Exception { | 4 | class MobileScannerException implements Exception { |
| 2 | - String message; | ||
| 3 | - MobileScannerException(this.message); | 5 | + const MobileScannerException({ |
| 6 | + required this.errorCode, | ||
| 7 | + this.errorDetails, | ||
| 8 | + }); | ||
| 9 | + | ||
| 10 | + /// The error code of the exception. | ||
| 11 | + final MobileScannerErrorCode errorCode; | ||
| 12 | + | ||
| 13 | + /// The additional error details that came with the [errorCode]. | ||
| 14 | + final MobileScannerErrorDetails? errorDetails; | ||
| 15 | + | ||
| 16 | + @override | ||
| 17 | + String toString() { | ||
| 18 | + if (errorDetails != null && errorDetails?.message != null) { | ||
| 19 | + return "MobileScannerException: code ${errorCode.name}, message: ${errorDetails?.message}"; | ||
| 20 | + } | ||
| 21 | + return "MobileScannerException: ${errorCode.name}"; | ||
| 22 | + } | ||
| 23 | +} | ||
| 24 | + | ||
| 25 | +/// The raw error details for a [MobileScannerException]. | ||
| 26 | +class MobileScannerErrorDetails { | ||
| 27 | + const MobileScannerErrorDetails({ | ||
| 28 | + this.code, | ||
| 29 | + this.details, | ||
| 30 | + this.message, | ||
| 31 | + }); | ||
| 32 | + | ||
| 33 | + /// The error code from the [PlatformException]. | ||
| 34 | + final String? code; | ||
| 35 | + | ||
| 36 | + /// The details from the [PlatformException]. | ||
| 37 | + final Object? details; | ||
| 38 | + | ||
| 39 | + /// The error message from the [PlatformException]. | ||
| 40 | + final String? message; | ||
| 4 | } | 41 | } |
| @@ -238,9 +238,7 @@ public class MobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHandler, | @@ -238,9 +238,7 @@ public class MobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHandler, | ||
| 238 | 238 | ||
| 239 | func toggleTorch(_ call: FlutterMethodCall, _ result: @escaping FlutterResult) { | 239 | func toggleTorch(_ call: FlutterMethodCall, _ result: @escaping FlutterResult) { |
| 240 | if (device == nil) { | 240 | if (device == nil) { |
| 241 | - result(FlutterError(code: "MobileScanner", | ||
| 242 | - message: "Called toggleTorch() while stopped!", | ||
| 243 | - details: nil)) | 241 | + result(nil) |
| 244 | return | 242 | return |
| 245 | } | 243 | } |
| 246 | do { | 244 | do { |
| @@ -260,9 +258,8 @@ public class MobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHandler, | @@ -260,9 +258,8 @@ public class MobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHandler, | ||
| 260 | 258 | ||
| 261 | func stop(_ result: FlutterResult) { | 259 | func stop(_ result: FlutterResult) { |
| 262 | if (device == nil) { | 260 | if (device == nil) { |
| 263 | - result(FlutterError(code: "MobileScanner", | ||
| 264 | - message: "Called stop() while already stopped!", | ||
| 265 | - details: nil)) | 261 | + result(nil) |
| 262 | + | ||
| 266 | return | 263 | return |
| 267 | } | 264 | } |
| 268 | captureSession.stopRunning() | 265 | captureSession.stopRunning() |
| 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.2 | 3 | +version: 3.0.0-beta.3 |
| 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