reimplement parts of the MobileScannerController using the platform interface
Showing
1 changed file
with
97 additions
and
429 deletions
| 1 | import 'dart:async'; | 1 | import 'dart:async'; |
| 2 | -import 'dart:io'; | ||
| 3 | -// ignore: unnecessary_import | ||
| 4 | -import 'dart:typed_data'; | ||
| 5 | 2 | ||
| 6 | import 'package:flutter/foundation.dart'; | 3 | import 'package:flutter/foundation.dart'; |
| 7 | import 'package:flutter/services.dart'; | 4 | import 'package:flutter/services.dart'; |
| @@ -12,53 +9,45 @@ import 'package:mobile_scanner/src/enums/mobile_scanner_error_code.dart'; | @@ -12,53 +9,45 @@ import 'package:mobile_scanner/src/enums/mobile_scanner_error_code.dart'; | ||
| 12 | import 'package:mobile_scanner/src/enums/mobile_scanner_state.dart'; | 9 | import 'package:mobile_scanner/src/enums/mobile_scanner_state.dart'; |
| 13 | import 'package:mobile_scanner/src/enums/torch_state.dart'; | 10 | import 'package:mobile_scanner/src/enums/torch_state.dart'; |
| 14 | import 'package:mobile_scanner/src/mobile_scanner_exception.dart'; | 11 | import 'package:mobile_scanner/src/mobile_scanner_exception.dart'; |
| 15 | -import 'package:mobile_scanner/src/objects/barcode.dart'; | 12 | +import 'package:mobile_scanner/src/mobile_scanner_platform_interface.dart'; |
| 16 | import 'package:mobile_scanner/src/objects/barcode_capture.dart'; | 13 | import 'package:mobile_scanner/src/objects/barcode_capture.dart'; |
| 17 | -import 'package:mobile_scanner/src/objects/mobile_scanner_arguments.dart'; | ||
| 18 | 14 | ||
| 19 | -/// The [MobileScannerController] holds all the logic of this plugin, | ||
| 20 | -/// where as the [MobileScanner] class is the frontend of this plugin. | ||
| 21 | -class MobileScannerController { | 15 | +/// The controller for the [MobileScanner] widget. |
| 16 | +class MobileScannerController extends ValueNotifier<MobileScannerState> { | ||
| 17 | + /// Construct a new [MobileScannerController] instance. | ||
| 22 | MobileScannerController({ | 18 | MobileScannerController({ |
| 23 | - this.facing = CameraFacing.back, | 19 | + this.cameraResolution, |
| 24 | this.detectionSpeed = DetectionSpeed.normal, | 20 | this.detectionSpeed = DetectionSpeed.normal, |
| 25 | - this.detectionTimeoutMs = 250, | ||
| 26 | - this.torchEnabled = false, | ||
| 27 | - this.formats, | 21 | + int detectionTimeoutMs = 250, |
| 22 | + this.facing = CameraFacing.back, | ||
| 23 | + this.formats = const <BarcodeFormat>[], | ||
| 28 | this.returnImage = false, | 24 | this.returnImage = false, |
| 29 | - @Deprecated( | ||
| 30 | - 'Instead, use the result of calling `start()` to determine if permissions were granted.', | ||
| 31 | - ) | ||
| 32 | - this.onPermissionSet, | ||
| 33 | - this.autoStart = true, | ||
| 34 | - this.cameraResolution, | 25 | + this.torchEnabled = false, |
| 35 | this.useNewCameraSelector = false, | 26 | this.useNewCameraSelector = false, |
| 36 | - }); | 27 | + }) : detectionTimeoutMs = detectionSpeed == DetectionSpeed.normal ? detectionTimeoutMs : 0, |
| 28 | + assert(detectionTimeoutMs >= 0, 'The detection timeout must be greater than or equal to 0.'), | ||
| 29 | + super(MobileScannerState.uninitialized(facing)); | ||
| 37 | 30 | ||
| 38 | - /// Select which camera should be used. | 31 | + /// The desired resolution for the camera. |
| 39 | /// | 32 | /// |
| 40 | - /// Default: CameraFacing.back | ||
| 41 | - final CameraFacing facing; | ||
| 42 | - | ||
| 43 | - /// Enable or disable the torch (Flash) on start | 33 | + /// When this value is provided, the camera will try to match this resolution, |
| 34 | + /// or fallback to the closest available resolution. | ||
| 35 | + /// When this is null, Android defaults to a resolution of 640x480. | ||
| 44 | /// | 36 | /// |
| 45 | - /// Default: disabled | ||
| 46 | - final bool torchEnabled; | ||
| 47 | - | ||
| 48 | - /// Set to true if you want to return the image buffer with the Barcode event | 37 | + /// Bear in mind that changing the resolution has an effect on the aspect ratio. |
| 49 | /// | 38 | /// |
| 50 | - /// Only supported on iOS and Android | ||
| 51 | - final bool returnImage; | ||
| 52 | - | ||
| 53 | - /// If provided, the scanner will only detect those specific formats | ||
| 54 | - final List<BarcodeFormat>? formats; | 39 | + /// When the camera orientation changes, |
| 40 | + /// the resolution will be flipped to match the new dimensions of the display. | ||
| 41 | + /// | ||
| 42 | + /// Currently only supported on Android. | ||
| 43 | + final Size? cameraResolution; | ||
| 55 | 44 | ||
| 56 | - /// Sets the speed of detections. | 45 | + /// The detection speed for the scanner. |
| 57 | /// | 46 | /// |
| 58 | - /// WARNING: DetectionSpeed.unrestricted can cause memory issues on some devices | 47 | + /// Defaults to [DetectionSpeed.normal]. |
| 59 | final DetectionSpeed detectionSpeed; | 48 | final DetectionSpeed detectionSpeed; |
| 60 | 49 | ||
| 61 | - /// Sets the timeout, in milliseconds, of the scanner. | 50 | + /// The detection timeout, in milliseconds, for the scanner. |
| 62 | /// | 51 | /// |
| 63 | /// This timeout is ignored if the [detectionSpeed] | 52 | /// This timeout is ignored if the [detectionSpeed] |
| 64 | /// is not set to [DetectionSpeed.normal]. | 53 | /// is not set to [DetectionSpeed.normal]. |
| @@ -67,444 +56,123 @@ class MobileScannerController { | @@ -67,444 +56,123 @@ class MobileScannerController { | ||
| 67 | /// which prevents memory issues on older devices. | 56 | /// which prevents memory issues on older devices. |
| 68 | final int detectionTimeoutMs; | 57 | final int detectionTimeoutMs; |
| 69 | 58 | ||
| 70 | - /// Automatically start the mobileScanner on initialization. | ||
| 71 | - final bool autoStart; | 59 | + /// The facing direction for the camera. |
| 60 | + /// | ||
| 61 | + /// Defaults to the back-facing camera. | ||
| 62 | + final CameraFacing facing; | ||
| 72 | 63 | ||
| 73 | - /// The desired resolution for the camera. | 64 | + /// The formats that the scanner should detect. |
| 74 | /// | 65 | /// |
| 75 | - /// When this value is provided, the camera will try to match this resolution, | ||
| 76 | - /// or fallback to the closest available resolution. | ||
| 77 | - /// When this is null, Android defaults to a resolution of 640x480. | 66 | + /// If this is empty, all supported formats are detected. |
| 67 | + final List<BarcodeFormat> formats; | ||
| 68 | + | ||
| 69 | + /// Whether scanned barcodes should contain the image | ||
| 70 | + /// that is embedded into the barcode. | ||
| 78 | /// | 71 | /// |
| 79 | - /// Bear in mind that changing the resolution has an effect on the aspect ratio. | 72 | + /// If this is false, [BarcodeCapture.image] will always be null. |
| 80 | /// | 73 | /// |
| 81 | - /// When the camera orientation changes, | ||
| 82 | - /// the resolution will be flipped to match the new dimensions of the display. | 74 | + /// Defaults to false, and is only supported on iOS and Android. |
| 75 | + final bool returnImage; | ||
| 76 | + | ||
| 77 | + /// Whether the flashlight should be turned on when the camera is started. | ||
| 83 | /// | 78 | /// |
| 84 | - /// Currently only supported on Android. | ||
| 85 | - final Size? cameraResolution; | 79 | + /// Defaults to false. |
| 80 | + final bool torchEnabled; | ||
| 86 | 81 | ||
| 87 | - /// Use the new resolution selector. Warning: not fully tested, may produce | ||
| 88 | - /// unwanted/zoomed images. | 82 | + /// Use the new resolution selector. |
| 83 | + /// | ||
| 84 | + /// This feature is experimental and not fully tested yet. | ||
| 85 | + /// Use caution when using this flag, | ||
| 86 | + /// as the new resolution selector may produce unwanted or zoomed images. | ||
| 89 | /// | 87 | /// |
| 90 | - /// Only supported on Android | 88 | + /// Only supported on Android. |
| 91 | final bool useNewCameraSelector; | 89 | final bool useNewCameraSelector; |
| 92 | 90 | ||
| 93 | - /// Sets the barcode stream | ||
| 94 | - final StreamController<BarcodeCapture> _barcodesController = | ||
| 95 | - StreamController.broadcast(); | 91 | + /// The internal barcode controller, that listens for detected barcodes. |
| 92 | + final StreamController<BarcodeCapture> _barcodesController = StreamController.broadcast(); | ||
| 96 | 93 | ||
| 94 | + /// Get the stream of scanned barcodes. | ||
| 97 | Stream<BarcodeCapture> get barcodes => _barcodesController.stream; | 95 | Stream<BarcodeCapture> get barcodes => _barcodesController.stream; |
| 98 | 96 | ||
| 99 | - static const MethodChannel _methodChannel = | ||
| 100 | - MethodChannel('dev.steenbakker.mobile_scanner/scanner/method'); | ||
| 101 | - static const EventChannel _eventChannel = | ||
| 102 | - EventChannel('dev.steenbakker.mobile_scanner/scanner/event'); | ||
| 103 | - | ||
| 104 | - @Deprecated( | ||
| 105 | - 'Instead, use the result of calling `start()` to determine if permissions were granted.', | ||
| 106 | - ) | ||
| 107 | - Function(bool permissionGranted)? onPermissionSet; | ||
| 108 | - | ||
| 109 | - /// Listen to events from the platform specific code | ||
| 110 | - StreamSubscription? events; | ||
| 111 | - | ||
| 112 | - /// A notifier that provides several arguments about the MobileScanner | ||
| 113 | - final ValueNotifier<MobileScannerArguments?> startArguments = | ||
| 114 | - ValueNotifier(null); | ||
| 115 | - | ||
| 116 | - /// A notifier that provides the state of the Torch (Flash) | ||
| 117 | - final ValueNotifier<TorchState> torchState = ValueNotifier(TorchState.off); | ||
| 118 | - | ||
| 119 | - /// A notifier that provides the state of which camera is being used | ||
| 120 | - late final ValueNotifier<CameraFacing> cameraFacingState = | ||
| 121 | - ValueNotifier(facing); | ||
| 122 | - | ||
| 123 | - /// A notifier that provides zoomScale. | ||
| 124 | - final ValueNotifier<double> zoomScaleState = ValueNotifier(0.0); | ||
| 125 | - | ||
| 126 | - bool isStarting = false; | ||
| 127 | - | ||
| 128 | - /// A notifier that provides availability of the Torch (Flash) | ||
| 129 | - final ValueNotifier<bool?> hasTorchState = ValueNotifier(false); | ||
| 130 | - | ||
| 131 | - /// Returns whether the device has a torch. | 97 | + /// Analyze an image file. |
| 132 | /// | 98 | /// |
| 133 | - /// Throws an error if the controller is not initialized. | ||
| 134 | - bool get hasTorch { | ||
| 135 | - final hasTorch = hasTorchState.value; | ||
| 136 | - if (hasTorch == null) { | ||
| 137 | - throw const MobileScannerException( | ||
| 138 | - errorCode: MobileScannerErrorCode.controllerUninitialized, | ||
| 139 | - ); | ||
| 140 | - } | ||
| 141 | - | ||
| 142 | - return hasTorch; | 99 | + /// The [path] points to a file on the device. |
| 100 | + /// | ||
| 101 | + /// This is only supported on Android and iOS. | ||
| 102 | + /// | ||
| 103 | + /// Returns the [BarcodeCapture] that was found in the image. | ||
| 104 | + Future<BarcodeCapture?> analyzeImage(String path) { | ||
| 105 | + return MobileScannerPlatform.instance.analyzeImage(path); | ||
| 143 | } | 106 | } |
| 144 | 107 | ||
| 145 | - /// Set the starting arguments for the camera | ||
| 146 | - Map<String, dynamic> _argumentsToMap({CameraFacing? cameraFacingOverride}) { | ||
| 147 | - final Map<String, dynamic> arguments = {}; | ||
| 148 | - | ||
| 149 | - cameraFacingState.value = cameraFacingOverride ?? facing; | ||
| 150 | - arguments['facing'] = cameraFacingState.value.rawValue; | ||
| 151 | - arguments['torch'] = torchEnabled; | ||
| 152 | - arguments['speed'] = detectionSpeed.rawValue; | ||
| 153 | - arguments['timeout'] = detectionTimeoutMs; | ||
| 154 | - arguments['returnImage'] = returnImage; | ||
| 155 | - arguments['useNewCameraSelector'] = useNewCameraSelector; | ||
| 156 | - | ||
| 157 | - /* if (scanWindow != null) { | ||
| 158 | - arguments['scanWindow'] = [ | ||
| 159 | - scanWindow!.left, | ||
| 160 | - scanWindow!.top, | ||
| 161 | - scanWindow!.right, | ||
| 162 | - scanWindow!.bottom, | ||
| 163 | - ]; | ||
| 164 | - } */ | ||
| 165 | - | ||
| 166 | - if (formats != null) { | ||
| 167 | - if (kIsWeb || Platform.isIOS || Platform.isMacOS || Platform.isAndroid) { | ||
| 168 | - arguments['formats'] = formats!.map((e) => e.rawValue).toList(); | ||
| 169 | - } | ||
| 170 | - } | ||
| 171 | - | ||
| 172 | - if (cameraResolution != null) { | ||
| 173 | - arguments['cameraResolution'] = <int>[ | ||
| 174 | - cameraResolution!.width.toInt(), | ||
| 175 | - cameraResolution!.height.toInt(), | ||
| 176 | - ]; | ||
| 177 | - } | ||
| 178 | - | ||
| 179 | - return arguments; | 108 | + /// Reset the zoom scale of the camera. |
| 109 | + Future<void> resetZoomScale() async { | ||
| 110 | + await MobileScannerPlatform.instance.resetZoomScale(); | ||
| 180 | } | 111 | } |
| 181 | 112 | ||
| 182 | - /// Start scanning for barcodes. | ||
| 183 | - /// Upon calling this method, the necessary camera permission will be requested. | 113 | + /// Set the zoom scale of the camera. |
| 184 | /// | 114 | /// |
| 185 | - /// Returns an instance of [MobileScannerArguments] | ||
| 186 | - /// when the scanner was successfully started. | ||
| 187 | - /// Returns null if the scanner is currently starting. | ||
| 188 | - /// | ||
| 189 | - /// Throws a [MobileScannerException] if starting the scanner failed. | ||
| 190 | - Future<MobileScannerArguments?> start({ | ||
| 191 | - CameraFacing? cameraFacingOverride, | ||
| 192 | - }) async { | ||
| 193 | - if (isStarting) { | ||
| 194 | - debugPrint("Called start() while starting."); | ||
| 195 | - return null; | ||
| 196 | - } | ||
| 197 | - | ||
| 198 | - events ??= _eventChannel | ||
| 199 | - .receiveBroadcastStream() | ||
| 200 | - .listen((data) => _handleEvent(data as Map)); | ||
| 201 | - | ||
| 202 | - isStarting = true; | ||
| 203 | - | ||
| 204 | - // Check authorization status | ||
| 205 | - if (!kIsWeb) { | ||
| 206 | - final MobileScannerState state; | ||
| 207 | - | ||
| 208 | - try { | ||
| 209 | - state = MobileScannerState.fromRawValue( | ||
| 210 | - await _methodChannel.invokeMethod('state') as int? ?? 0, | ||
| 211 | - ); | ||
| 212 | - } on PlatformException catch (error) { | ||
| 213 | - isStarting = false; | ||
| 214 | - | ||
| 215 | - throw MobileScannerException( | ||
| 216 | - errorCode: MobileScannerErrorCode.genericError, | ||
| 217 | - errorDetails: MobileScannerErrorDetails( | ||
| 218 | - code: error.code, | ||
| 219 | - details: error.details as Object?, | ||
| 220 | - message: error.message, | ||
| 221 | - ), | ||
| 222 | - ); | ||
| 223 | - } | ||
| 224 | - | ||
| 225 | - switch (state) { | ||
| 226 | - // Android does not have an undetermined permission state. | ||
| 227 | - // So if the permission state is denied, just request it now. | ||
| 228 | - case MobileScannerState.undetermined: | ||
| 229 | - case MobileScannerState.denied: | ||
| 230 | - try { | ||
| 231 | - final bool granted = | ||
| 232 | - await _methodChannel.invokeMethod('request') as bool? ?? false; | ||
| 233 | - | ||
| 234 | - if (!granted) { | ||
| 235 | - isStarting = false; | ||
| 236 | - throw const MobileScannerException( | ||
| 237 | - errorCode: MobileScannerErrorCode.permissionDenied, | ||
| 238 | - ); | ||
| 239 | - } | ||
| 240 | - } on PlatformException catch (error) { | ||
| 241 | - isStarting = false; | ||
| 242 | - throw MobileScannerException( | ||
| 243 | - errorCode: MobileScannerErrorCode.genericError, | ||
| 244 | - errorDetails: MobileScannerErrorDetails( | ||
| 245 | - code: error.code, | ||
| 246 | - details: error.details as Object?, | ||
| 247 | - message: error.message, | ||
| 248 | - ), | ||
| 249 | - ); | ||
| 250 | - } | ||
| 251 | - | ||
| 252 | - case MobileScannerState.authorized: | ||
| 253 | - break; | ||
| 254 | - } | ||
| 255 | - } | ||
| 256 | - | ||
| 257 | - // Start the camera with arguments | ||
| 258 | - Map<String, dynamic>? startResult = {}; | ||
| 259 | - try { | ||
| 260 | - startResult = await _methodChannel.invokeMapMethod<String, dynamic>( | ||
| 261 | - 'start', | ||
| 262 | - _argumentsToMap(cameraFacingOverride: cameraFacingOverride), | ||
| 263 | - ); | ||
| 264 | - } on PlatformException catch (error) { | ||
| 265 | - MobileScannerErrorCode errorCode = MobileScannerErrorCode.genericError; | ||
| 266 | - | ||
| 267 | - final String? errorMessage = error.message; | ||
| 268 | - | ||
| 269 | - if (kIsWeb) { | ||
| 270 | - if (errorMessage == null) { | ||
| 271 | - errorCode = MobileScannerErrorCode.genericError; | ||
| 272 | - } else if (errorMessage.contains('NotFoundError') || | ||
| 273 | - errorMessage.contains('NotSupportedError')) { | ||
| 274 | - errorCode = MobileScannerErrorCode.unsupported; | ||
| 275 | - } else if (errorMessage.contains('NotAllowedError')) { | ||
| 276 | - errorCode = MobileScannerErrorCode.permissionDenied; | ||
| 277 | - } else { | ||
| 278 | - errorCode = MobileScannerErrorCode.genericError; | ||
| 279 | - } | ||
| 280 | - } | ||
| 281 | - | ||
| 282 | - isStarting = false; | ||
| 283 | - | ||
| 284 | - throw MobileScannerException( | ||
| 285 | - errorCode: errorCode, | ||
| 286 | - errorDetails: MobileScannerErrorDetails( | ||
| 287 | - code: error.code, | ||
| 288 | - details: error.details as Object?, | ||
| 289 | - message: error.message, | ||
| 290 | - ), | ||
| 291 | - ); | ||
| 292 | - } | ||
| 293 | - | ||
| 294 | - if (startResult == null) { | ||
| 295 | - isStarting = false; | 115 | + /// The [zoomScale] must be between 0.0 and 1.0 (both inclusive). |
| 116 | + Future<void> setZoomScale(double zoomScale) async { | ||
| 117 | + if (zoomScale < 0 || zoomScale > 1) { | ||
| 296 | throw const MobileScannerException( | 118 | throw const MobileScannerException( |
| 297 | errorCode: MobileScannerErrorCode.genericError, | 119 | errorCode: MobileScannerErrorCode.genericError, |
| 120 | + errorDetails: MobileScannerErrorDetails( | ||
| 121 | + message: 'The zoomScale must be between 0.0 and 1.0', | ||
| 122 | + ), | ||
| 298 | ); | 123 | ); |
| 299 | } | 124 | } |
| 300 | 125 | ||
| 301 | - final hasTorch = startResult['torchable'] as bool? ?? false; | ||
| 302 | - hasTorchState.value = hasTorch; | ||
| 303 | - | ||
| 304 | - final Size size; | ||
| 305 | - | ||
| 306 | - if (kIsWeb) { | ||
| 307 | - size = Size( | ||
| 308 | - startResult['videoWidth'] as double? ?? 0, | ||
| 309 | - startResult['videoHeight'] as double? ?? 0, | ||
| 310 | - ); | ||
| 311 | - } else { | ||
| 312 | - final Map<Object?, Object?>? sizeInfo = | ||
| 313 | - startResult['size'] as Map<Object?, Object?>?; | ||
| 314 | - | ||
| 315 | - size = Size( | ||
| 316 | - sizeInfo?['width'] as double? ?? 0, | ||
| 317 | - sizeInfo?['height'] as double? ?? 0, | ||
| 318 | - ); | ||
| 319 | - } | ||
| 320 | - | ||
| 321 | - isStarting = false; | ||
| 322 | - return startArguments.value = MobileScannerArguments( | ||
| 323 | - numberOfCameras: startResult['numberOfCameras'] as int?, | ||
| 324 | - size: size, | ||
| 325 | - hasTorch: hasTorch, | ||
| 326 | - textureId: kIsWeb ? null : startResult['textureId'] as int?, | ||
| 327 | - webId: kIsWeb ? startResult['ViewID'] as String? : null, | ||
| 328 | - ); | 126 | + await MobileScannerPlatform.instance.setZoomScale(zoomScale); |
| 329 | } | 127 | } |
| 330 | 128 | ||
| 331 | - /// Stops the camera, but does not dispose this controller. | 129 | + /// Stop the camera. |
| 130 | + /// | ||
| 131 | + /// After calling this method, the camera can be restarted using [start]. | ||
| 332 | Future<void> stop() async { | 132 | Future<void> stop() async { |
| 333 | - await _methodChannel.invokeMethod('stop'); | 133 | + await MobileScannerPlatform.instance.stop(); |
| 334 | 134 | ||
| 335 | // After the camera stopped, set the torch state to off, | 135 | // After the camera stopped, set the torch state to off, |
| 336 | // as the torch state callback is never called when the camera is stopped. | 136 | // as the torch state callback is never called when the camera is stopped. |
| 337 | torchState.value = TorchState.off; | 137 | torchState.value = TorchState.off; |
| 338 | } | 138 | } |
| 339 | 139 | ||
| 340 | - /// Switches the torch on or off. | ||
| 341 | - /// | ||
| 342 | - /// Does nothing if the device has no torch. | ||
| 343 | - /// | ||
| 344 | - /// Throws if the controller was not initialized. | ||
| 345 | - Future<void> toggleTorch() async { | ||
| 346 | - final hasTorch = hasTorchState.value; | ||
| 347 | - | ||
| 348 | - if (hasTorch == null) { | ||
| 349 | - throw const MobileScannerException( | ||
| 350 | - errorCode: MobileScannerErrorCode.controllerUninitialized, | ||
| 351 | - ); | ||
| 352 | - } | 140 | + /// Switch between the front and back camera. |
| 141 | + Future<void> switchCamera() async { | ||
| 142 | + await MobileScannerPlatform.instance.stop(); | ||
| 353 | 143 | ||
| 354 | - if (!hasTorch) { | ||
| 355 | - return; | ||
| 356 | - } | 144 | + final CameraFacing cameraDirection; |
| 357 | 145 | ||
| 358 | - final TorchState newState = | ||
| 359 | - torchState.value == TorchState.off ? TorchState.on : TorchState.off; | 146 | + // TODO: update the camera facing direction state |
| 360 | 147 | ||
| 361 | - await _methodChannel.invokeMethod('torch', newState.rawValue); | 148 | + await start(cameraDirection: cameraDirection); |
| 362 | } | 149 | } |
| 363 | 150 | ||
| 364 | - /// Changes the state of the camera (front or back). | 151 | + /// Switches the flashlight on or off. |
| 365 | /// | 152 | /// |
| 366 | - /// Does nothing if the device has no front camera. | ||
| 367 | - Future<void> switchCamera() async { | ||
| 368 | - await _methodChannel.invokeMethod('stop'); | ||
| 369 | - final CameraFacing facingToUse = | ||
| 370 | - cameraFacingState.value == CameraFacing.back | ||
| 371 | - ? CameraFacing.front | ||
| 372 | - : CameraFacing.back; | ||
| 373 | - await start(cameraFacingOverride: facingToUse); | ||
| 374 | - } | ||
| 375 | - | ||
| 376 | - /// Handles a local image file. | ||
| 377 | - /// Returns true if a barcode or QR code is found. | ||
| 378 | - /// Returns false if nothing is found. | 153 | + /// Does nothing if the device has no torch. |
| 379 | /// | 154 | /// |
| 380 | - /// [path] The path of the image on the devices | ||
| 381 | - Future<bool> analyzeImage(String path) async { | ||
| 382 | - events ??= _eventChannel | ||
| 383 | - .receiveBroadcastStream() | ||
| 384 | - .listen((data) => _handleEvent(data as Map)); | ||
| 385 | - | ||
| 386 | - return _methodChannel | ||
| 387 | - .invokeMethod<bool>('analyzeImage', path) | ||
| 388 | - .then<bool>((bool? value) => value ?? false); | ||
| 389 | - } | 155 | + /// Throws if the controller was not initialized. |
| 156 | + Future<void> toggleTorch() async { | ||
| 157 | + final bool hasTorch; | ||
| 390 | 158 | ||
| 391 | - /// Set the zoomScale of the camera. | ||
| 392 | - /// | ||
| 393 | - /// [zoomScale] must be within 0.0 and 1.0, where 1.0 is the max zoom, and 0.0 | ||
| 394 | - /// is zoomed out. | ||
| 395 | - Future<void> setZoomScale(double zoomScale) async { | ||
| 396 | - if (zoomScale < 0 || zoomScale > 1) { | ||
| 397 | - throw const MobileScannerException( | ||
| 398 | - errorCode: MobileScannerErrorCode.genericError, | ||
| 399 | - errorDetails: MobileScannerErrorDetails( | ||
| 400 | - message: 'The zoomScale must be between 0 and 1.', | ||
| 401 | - ), | ||
| 402 | - ); | 159 | + if (!hasTorch) { |
| 160 | + return; | ||
| 403 | } | 161 | } |
| 404 | - await _methodChannel.invokeMethod('setScale', zoomScale); | ||
| 405 | - } | ||
| 406 | 162 | ||
| 407 | - /// Reset the zoomScale of the camera to use standard scale 1x. | ||
| 408 | - Future<void> resetZoomScale() async { | ||
| 409 | - await _methodChannel.invokeMethod('resetScale'); | ||
| 410 | - } | 163 | + final TorchState newState = torchState.value == TorchState.off ? TorchState.on : TorchState.off; |
| 411 | 164 | ||
| 412 | - /// Disposes the MobileScannerController and closes all listeners. | ||
| 413 | - /// | ||
| 414 | - /// If you call this, you cannot use this controller object anymore. | ||
| 415 | - void dispose() { | ||
| 416 | - stop(); | ||
| 417 | - events?.cancel(); | ||
| 418 | - _barcodesController.close(); | 165 | + // Update the torch state to the new state. |
| 166 | + // When the platform has updated the torch state, | ||
| 167 | + // it will send an update through the torch state event stream. | ||
| 168 | + await MobileScannerPlatform.instance.setTorchState(); | ||
| 419 | } | 169 | } |
| 420 | 170 | ||
| 421 | - /// Handles a returning event from the platform side | ||
| 422 | - void _handleEvent(Map event) { | ||
| 423 | - final name = event['name']; | ||
| 424 | - final data = event['data']; | ||
| 425 | - | ||
| 426 | - switch (name) { | ||
| 427 | - case 'torchState': | ||
| 428 | - final state = TorchState.values[data as int? ?? 0]; | ||
| 429 | - torchState.value = state; | ||
| 430 | - case 'zoomScaleState': | ||
| 431 | - zoomScaleState.value = data as double? ?? 0.0; | ||
| 432 | - case 'barcode': | ||
| 433 | - if (data == null) return; | ||
| 434 | - final parsed = (data as List) | ||
| 435 | - .map((value) => Barcode.fromNative(value as Map)) | ||
| 436 | - .toList(); | ||
| 437 | - | ||
| 438 | - final double? width = event['width'] as double?; | ||
| 439 | - final double? height = event['height'] as double?; | ||
| 440 | - | ||
| 441 | - _barcodesController.add( | ||
| 442 | - BarcodeCapture( | ||
| 443 | - raw: data, | ||
| 444 | - barcodes: parsed, | ||
| 445 | - image: event['image'] as Uint8List?, | ||
| 446 | - size: width == null || height == null | ||
| 447 | - ? Size.zero | ||
| 448 | - : Size(width, height), | ||
| 449 | - ), | ||
| 450 | - ); | ||
| 451 | - case 'barcodeMac': | ||
| 452 | - _barcodesController.add( | ||
| 453 | - BarcodeCapture( | ||
| 454 | - raw: data, | ||
| 455 | - barcodes: [ | ||
| 456 | - Barcode( | ||
| 457 | - rawValue: (data as Map)['payload'] as String?, | ||
| 458 | - format: BarcodeFormat.fromRawValue( | ||
| 459 | - data['symbology'] as int? ?? -1, | ||
| 460 | - ), | ||
| 461 | - ), | ||
| 462 | - ], | ||
| 463 | - ), | ||
| 464 | - ); | ||
| 465 | - case 'barcodeWeb': | ||
| 466 | - final barcode = data as Map?; | ||
| 467 | - final corners = barcode?['corners'] as List<Object?>? ?? <Object?>[]; | ||
| 468 | - | ||
| 469 | - _barcodesController.add( | ||
| 470 | - BarcodeCapture( | ||
| 471 | - raw: data, | ||
| 472 | - barcodes: [ | ||
| 473 | - if (barcode != null) | ||
| 474 | - Barcode( | ||
| 475 | - rawValue: barcode['rawValue'] as String?, | ||
| 476 | - rawBytes: barcode['rawBytes'] as Uint8List?, | ||
| 477 | - format: BarcodeFormat.fromRawValue( | ||
| 478 | - barcode['format'] as int? ?? -1, | ||
| 479 | - ), | ||
| 480 | - corners: List.unmodifiable( | ||
| 481 | - corners.cast<Map<Object?, Object?>>().map( | ||
| 482 | - (Map<Object?, Object?> e) { | ||
| 483 | - return Offset(e['x']! as double, e['y']! as double); | ||
| 484 | - }, | ||
| 485 | - ), | ||
| 486 | - ), | ||
| 487 | - ), | ||
| 488 | - ], | ||
| 489 | - ), | ||
| 490 | - ); | ||
| 491 | - case 'error': | ||
| 492 | - throw MobileScannerException( | ||
| 493 | - errorCode: MobileScannerErrorCode.genericError, | ||
| 494 | - errorDetails: MobileScannerErrorDetails(message: data as String?), | ||
| 495 | - ); | ||
| 496 | - default: | ||
| 497 | - throw UnimplementedError(name as String?); | ||
| 498 | - } | ||
| 499 | - } | ||
| 500 | - | ||
| 501 | - /// updates the native ScanWindow | ||
| 502 | - Future<void> updateScanWindow(Rect? window) async { | ||
| 503 | - List? data; | ||
| 504 | - if (window != null) { | ||
| 505 | - data = [window.left, window.top, window.right, window.bottom]; | ||
| 506 | - } | 171 | + @override |
| 172 | + Future<void> dispose() async { | ||
| 173 | + await MobileScannerPlatform.instance.dispose(); | ||
| 174 | + unawaited(_barcodesController.close()); | ||
| 507 | 175 | ||
| 508 | - await _methodChannel.invokeMethod('updateScanWindow', {'rect': data}); | 176 | + super.dispose(); |
| 509 | } | 177 | } |
| 510 | } | 178 | } |
-
Please register or login to post a comment