Committed by
GitHub
Merge branch 'master' into dependabot/gradle/example/android/kotlin_version-1.7.21
Showing
31 changed files
with
828 additions
and
583 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 | +* onDetect now returns the object BarcodeCapture, which contains a List of barcodes and, if enabled, an image. | ||
| 6 | +* allowDuplicates is removed and replaced by MobileScannerSpeed enum. | ||
| 7 | +* onPermissionSet in MobileScanner widget is deprecated and will be removed. Use the onPermissionSet | ||
| 8 | +onPermissionSet callback in MobileScannerController instead. | ||
| 3 | * [iOS] The minimum deployment target is now 11.0 or higher. | 9 | * [iOS] The minimum deployment target is now 11.0 or higher. |
| 10 | + | ||
| 11 | +Features: | ||
| 12 | +* The returnImage is working for both iOS and Android. You can enable it in the MobileScannerController. | ||
| 13 | +The image will be returned in the BarcodeCapture object provided by onDetect. | ||
| 14 | +* You can now control the DetectionSpeed, as well as the timeout of the DetectionSpeed. For more | ||
| 15 | +info see the DetectionSpeed documentation. This replaces the allowDuplicates function. | ||
| 16 | + | ||
| 17 | +Other improvements: | ||
| 18 | +* Both the [iOS] and [Android] codebases have been refactored completely. | ||
| 4 | * [iOS] Updated POD dependencies | 19 | * [iOS] Updated POD dependencies |
| 5 | 20 | ||
| 6 | ## 3.0.0-beta.1 | 21 | ## 3.0.0-beta.1 |
| @@ -75,15 +75,15 @@ import 'package:mobile_scanner/mobile_scanner.dart'; | @@ -75,15 +75,15 @@ import 'package:mobile_scanner/mobile_scanner.dart'; | ||
| 75 | return Scaffold( | 75 | return Scaffold( |
| 76 | appBar: AppBar(title: const Text('Mobile Scanner')), | 76 | appBar: AppBar(title: const Text('Mobile Scanner')), |
| 77 | body: MobileScanner( | 77 | body: MobileScanner( |
| 78 | - allowDuplicates: false, | ||
| 79 | - onDetect: (barcode, args) { | ||
| 80 | - if (barcode.rawValue == null) { | ||
| 81 | - debugPrint('Failed to scan Barcode'); | ||
| 82 | - } else { | ||
| 83 | - final String code = barcode.rawValue!; | ||
| 84 | - debugPrint('Barcode found! $code'); | 78 | + // fit: BoxFit.contain, |
| 79 | + onDetect: (capture) { | ||
| 80 | + final List<Barcode> barcodes = capture.barcodes; | ||
| 81 | + final Uint8List? image = capture.image; | ||
| 82 | + for (final barcode in barcodes) { | ||
| 83 | + debugPrint('Barcode found! ${barcode.rawValue}'); | ||
| 85 | } | 84 | } |
| 86 | - }), | 85 | + }, |
| 86 | + ), | ||
| 87 | ); | 87 | ); |
| 88 | } | 88 | } |
| 89 | ``` | 89 | ``` |
| @@ -98,17 +98,18 @@ import 'package:mobile_scanner/mobile_scanner.dart'; | @@ -98,17 +98,18 @@ import 'package:mobile_scanner/mobile_scanner.dart'; | ||
| 98 | return Scaffold( | 98 | return Scaffold( |
| 99 | appBar: AppBar(title: const Text('Mobile Scanner')), | 99 | appBar: AppBar(title: const Text('Mobile Scanner')), |
| 100 | body: MobileScanner( | 100 | body: MobileScanner( |
| 101 | - allowDuplicates: false, | 101 | + // fit: BoxFit.contain, |
| 102 | controller: MobileScannerController( | 102 | controller: MobileScannerController( |
| 103 | - facing: CameraFacing.front, torchEnabled: true), | ||
| 104 | - onDetect: (barcode, args) { | ||
| 105 | - if (barcode.rawValue == null) { | ||
| 106 | - debugPrint('Failed to scan Barcode'); | ||
| 107 | - } else { | ||
| 108 | - final String code = barcode.rawValue!; | ||
| 109 | - debugPrint('Barcode found! $code'); | 103 | + facing: CameraFacing.front, torchEnabled: true, |
| 104 | + ), | ||
| 105 | + onDetect: (capture) { | ||
| 106 | + final List<Barcode> barcodes = capture.barcodes; | ||
| 107 | + final Uint8List? image = capture.image; | ||
| 108 | + for (final barcode in barcodes) { | ||
| 109 | + debugPrint('Barcode found! ${barcode.rawValue}'); | ||
| 110 | } | 110 | } |
| 111 | - }), | 111 | + }, |
| 112 | + ), | ||
| 112 | ); | 113 | ); |
| 113 | } | 114 | } |
| 114 | ``` | 115 | ``` |
| @@ -161,16 +162,17 @@ import 'package:mobile_scanner/mobile_scanner.dart'; | @@ -161,16 +162,17 @@ import 'package:mobile_scanner/mobile_scanner.dart'; | ||
| 161 | ], | 162 | ], |
| 162 | ), | 163 | ), |
| 163 | body: MobileScanner( | 164 | body: MobileScanner( |
| 164 | - allowDuplicates: false, | 165 | + // fit: BoxFit.contain, |
| 165 | controller: cameraController, | 166 | controller: cameraController, |
| 166 | - onDetect: (barcode, args) { | ||
| 167 | - if (barcode.rawValue == null) { | ||
| 168 | - debugPrint('Failed to scan Barcode'); | ||
| 169 | - } else { | ||
| 170 | - final String code = barcode.rawValue!; | ||
| 171 | - debugPrint('Barcode found! $code'); | 167 | + onDetect: (capture) { |
| 168 | + final List<Barcode> barcodes = capture.barcodes; | ||
| 169 | + final Uint8List? image = capture.image; | ||
| 170 | + for (final barcode in barcodes) { | ||
| 171 | + debugPrint('Barcode found! ${barcode.rawValue}'); | ||
| 172 | } | 172 | } |
| 173 | - })); | 173 | + }, |
| 174 | + ), | ||
| 175 | + ); | ||
| 174 | } | 176 | } |
| 175 | ``` | 177 | ``` |
| 176 | 178 | ||
| @@ -184,25 +186,25 @@ import 'package:mobile_scanner/mobile_scanner.dart'; | @@ -184,25 +186,25 @@ import 'package:mobile_scanner/mobile_scanner.dart'; | ||
| 184 | return Scaffold( | 186 | return Scaffold( |
| 185 | appBar: AppBar(title: const Text('Mobile Scanner')), | 187 | appBar: AppBar(title: const Text('Mobile Scanner')), |
| 186 | body: MobileScanner( | 188 | body: MobileScanner( |
| 189 | + fit: BoxFit.contain, | ||
| 187 | controller: MobileScannerController( | 190 | controller: MobileScannerController( |
| 188 | - facing: CameraFacing.front, | ||
| 189 | - torchEnabled: true, | 191 | + // facing: CameraFacing.back, |
| 192 | + // torchEnabled: false, | ||
| 190 | returnImage: true, | 193 | returnImage: true, |
| 191 | ), | 194 | ), |
| 192 | - onDetect: (barcode, args) { | ||
| 193 | - if (barcode.rawValue == null) { | ||
| 194 | - debugPrint('Failed to scan Barcode'); | ||
| 195 | - } else { | ||
| 196 | - final String code = barcode.rawValue!; | ||
| 197 | - debugPrint('Barcode found! $code'); | ||
| 198 | - | ||
| 199 | - debugPrint( | ||
| 200 | - 'Image returned! length: ${barcode.image!.lengthInBytes}b'); | 195 | + onDetect: (capture) { |
| 196 | + final List<Barcode> barcodes = capture.barcodes; | ||
| 197 | + final Uint8List? image = capture.image; | ||
| 198 | + for (final barcode in barcodes) { | ||
| 199 | + debugPrint('Barcode found! ${barcode.rawValue}'); | ||
| 200 | + } | ||
| 201 | + if (image != null) { | ||
| 201 | showDialog( | 202 | showDialog( |
| 202 | context: context, | 203 | context: context, |
| 203 | - builder: (context) => Image(image: MemoryImage(barcode.image!)), | 204 | + builder: (context) => |
| 205 | + Image(image: MemoryImage(image)), | ||
| 204 | ); | 206 | ); |
| 205 | - Future.delayed(const Duration(seconds: 2), () { | 207 | + Future.delayed(const Duration(seconds: 5), () { |
| 206 | Navigator.pop(context); | 208 | Navigator.pop(context); |
| 207 | }); | 209 | }); |
| 208 | } | 210 | } |
| @@ -212,25 +214,30 @@ import 'package:mobile_scanner/mobile_scanner.dart'; | @@ -212,25 +214,30 @@ import 'package:mobile_scanner/mobile_scanner.dart'; | ||
| 212 | } | 214 | } |
| 213 | ``` | 215 | ``` |
| 214 | 216 | ||
| 215 | -### Scan result | ||
| 216 | - | ||
| 217 | -You can use the following properties of the Barcode, which gets | ||
| 218 | -passed to the `onDetect` function. | ||
| 219 | - | ||
| 220 | -| Property name | Type | Description | ||
| 221 | -|---------------|----------------|-------------------- | ||
| 222 | -| image | Uint8List? | only if returnImage was set to true | ||
| 223 | -| format | BarcodeFormat | | ||
| 224 | -| rawBytes | Uint8List? | binary scan result | ||
| 225 | -| rawValue | String? | Value if barcode is in UTF-8 format | ||
| 226 | -| displayValue | String? | | ||
| 227 | -| type | BarcodeType | | ||
| 228 | -| calendarEvent | CalendarEvent? | | ||
| 229 | -| contactInfo | ContactInfo? | | ||
| 230 | -| driverLicense | DriverLicense? | | ||
| 231 | -| email | Email? | | ||
| 232 | -| geoPoint | GeoPoint? | | ||
| 233 | -| phone | Phone? | | ||
| 234 | -| sms | SMS? | | ||
| 235 | -| url | UrlBookmark? | | ||
| 236 | -| wifi | WiFi? | WiFi Access-Point details | 217 | +### BarcodeCapture |
| 218 | + | ||
| 219 | +The onDetect function returns a BarcodeCapture objects which contains the following items. | ||
| 220 | + | ||
| 221 | +| Property name | Type | Description | | ||
| 222 | +|---------------|---------------|-----------------------------------| | ||
| 223 | +| barcodes | List<Barcode> | A list with scanned barcodes. | | ||
| 224 | +| image | Uint8List? | If enabled, an image of the scan. | | ||
| 225 | + | ||
| 226 | +You can use the following properties of the Barcode object. | ||
| 227 | + | ||
| 228 | +| Property name | Type | Description | | ||
| 229 | +|---------------|----------------|-------------------------------------| | ||
| 230 | +| format | BarcodeFormat | | | ||
| 231 | +| rawBytes | Uint8List? | binary scan result | | ||
| 232 | +| rawValue | String? | Value if barcode is in UTF-8 format | | ||
| 233 | +| displayValue | String? | | | ||
| 234 | +| type | BarcodeType | | | ||
| 235 | +| calendarEvent | CalendarEvent? | | | ||
| 236 | +| contactInfo | ContactInfo? | | | ||
| 237 | +| driverLicense | DriverLicense? | | | ||
| 238 | +| email | Email? | | | ||
| 239 | +| geoPoint | GeoPoint? | | | ||
| 240 | +| phone | Phone? | | | ||
| 241 | +| sms | SMS? | | | ||
| 242 | +| url | UrlBookmark? | | | ||
| 243 | +| wifi | WiFi? | WiFi Access-Point details | |
| @@ -2,7 +2,7 @@ group 'dev.steenbakker.mobile_scanner' | @@ -2,7 +2,7 @@ group 'dev.steenbakker.mobile_scanner' | ||
| 2 | version '1.0-SNAPSHOT' | 2 | version '1.0-SNAPSHOT' |
| 3 | 3 | ||
| 4 | buildscript { | 4 | buildscript { |
| 5 | - ext.kotlin_version = '1.7.20' | 5 | + ext.kotlin_version = '1.7.21' |
| 6 | repositories { | 6 | repositories { |
| 7 | google() | 7 | google() |
| 8 | mavenCentral() | 8 | mavenCentral() |
| 1 | -package dev.steenbakker.mobile_scanner | ||
| 2 | - | ||
| 3 | -import androidx.annotation.IntDef | ||
| 4 | - | ||
| 5 | -@IntDef(AnalyzeMode.NONE, AnalyzeMode.BARCODE) | ||
| 6 | -@Target(AnnotationTarget.FIELD) | ||
| 7 | -@Retention(AnnotationRetention.SOURCE) | ||
| 8 | -annotation class AnalyzeMode { | ||
| 9 | - companion object { | ||
| 10 | - const val NONE = 0 | ||
| 11 | - const val BARCODE = 1 | ||
| 12 | - } | ||
| 13 | -} |
| 1 | +package dev.steenbakker.mobile_scanner | ||
| 2 | + | ||
| 3 | +import android.os.Handler | ||
| 4 | +import android.os.Looper | ||
| 5 | +import io.flutter.embedding.engine.plugins.FlutterPlugin | ||
| 6 | +import io.flutter.plugin.common.EventChannel | ||
| 7 | + | ||
| 8 | +class BarcodeHandler(flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) : EventChannel.StreamHandler { | ||
| 9 | + | ||
| 10 | + private var eventSink: EventChannel.EventSink? = null | ||
| 11 | + | ||
| 12 | + private val eventChannel = EventChannel( | ||
| 13 | + flutterPluginBinding.binaryMessenger, | ||
| 14 | + "dev.steenbakker.mobile_scanner/scanner/event" | ||
| 15 | + ) | ||
| 16 | + | ||
| 17 | + init { | ||
| 18 | + eventChannel.setStreamHandler(this) | ||
| 19 | + } | ||
| 20 | + | ||
| 21 | + fun publishEvent(event: Map<String, Any>) { | ||
| 22 | + Handler(Looper.getMainLooper()).post { | ||
| 23 | + eventSink?.success(event) | ||
| 24 | + } | ||
| 25 | + } | ||
| 26 | + | ||
| 27 | + override fun onListen(event: Any?, eventSink: EventChannel.EventSink?) { | ||
| 28 | + this.eventSink = eventSink | ||
| 29 | + } | ||
| 30 | + | ||
| 31 | + override fun onCancel(event: Any?) { | ||
| 32 | + this.eventSink = null | ||
| 33 | + } | ||
| 34 | +} |
| @@ -3,16 +3,10 @@ package dev.steenbakker.mobile_scanner | @@ -3,16 +3,10 @@ package dev.steenbakker.mobile_scanner | ||
| 3 | import android.Manifest | 3 | import android.Manifest |
| 4 | import android.app.Activity | 4 | import android.app.Activity |
| 5 | import android.content.pm.PackageManager | 5 | import android.content.pm.PackageManager |
| 6 | -import android.graphics.ImageFormat | ||
| 7 | -import android.graphics.Point | ||
| 8 | -import android.graphics.Rect | ||
| 9 | -import android.graphics.YuvImage | ||
| 10 | -import android.media.Image | ||
| 11 | import android.net.Uri | 6 | import android.net.Uri |
| 12 | -import android.util.Log | ||
| 13 | -import android.util.Size | 7 | +import android.os.Handler |
| 8 | +import android.os.Looper | ||
| 14 | import android.view.Surface | 9 | import android.view.Surface |
| 15 | -import androidx.annotation.NonNull | ||
| 16 | import androidx.camera.core.* | 10 | import androidx.camera.core.* |
| 17 | import androidx.camera.lifecycle.ProcessCameraProvider | 11 | import androidx.camera.lifecycle.ProcessCameraProvider |
| 18 | import androidx.core.app.ActivityCompat | 12 | import androidx.core.app.ActivityCompat |
| @@ -20,19 +14,32 @@ import androidx.core.content.ContextCompat | @@ -20,19 +14,32 @@ import androidx.core.content.ContextCompat | ||
| 20 | import androidx.lifecycle.LifecycleOwner | 14 | import androidx.lifecycle.LifecycleOwner |
| 21 | import com.google.mlkit.vision.barcode.BarcodeScannerOptions | 15 | import com.google.mlkit.vision.barcode.BarcodeScannerOptions |
| 22 | import com.google.mlkit.vision.barcode.BarcodeScanning | 16 | import com.google.mlkit.vision.barcode.BarcodeScanning |
| 23 | -import com.google.mlkit.vision.barcode.common.Barcode | ||
| 24 | import com.google.mlkit.vision.common.InputImage | 17 | import com.google.mlkit.vision.common.InputImage |
| 25 | -import io.flutter.plugin.common.EventChannel | ||
| 26 | -import io.flutter.plugin.common.MethodCall | ||
| 27 | -import io.flutter.plugin.common.MethodChannel | 18 | +import dev.steenbakker.mobile_scanner.objects.DetectionSpeed |
| 19 | +import dev.steenbakker.mobile_scanner.objects.MobileScannerStartParameters | ||
| 28 | import io.flutter.plugin.common.PluginRegistry | 20 | import io.flutter.plugin.common.PluginRegistry |
| 29 | import io.flutter.view.TextureRegistry | 21 | import io.flutter.view.TextureRegistry |
| 30 | -import java.io.ByteArrayOutputStream | ||
| 31 | -import java.io.File | ||
| 32 | 22 | ||
| 33 | - | ||
| 34 | -class MobileScanner(private val activity: Activity, private val textureRegistry: TextureRegistry) : | ||
| 35 | - MethodChannel.MethodCallHandler, EventChannel.StreamHandler, | 23 | +typealias PermissionCallback = (permissionGranted: Boolean) -> Unit |
| 24 | +typealias MobileScannerCallback = (barcodes: List<Map<String, Any?>>, image: ByteArray?) -> Unit | ||
| 25 | +typealias AnalyzerCallback = (barcodes: List<Map<String, Any?>>?) -> Unit | ||
| 26 | +typealias MobileScannerErrorCallback = (error: String) -> Unit | ||
| 27 | +typealias TorchStateCallback = (state: Int) -> Unit | ||
| 28 | +typealias MobileScannerStartedCallback = (parameters: MobileScannerStartParameters) -> Unit | ||
| 29 | + | ||
| 30 | +class NoCamera : Exception() | ||
| 31 | +class AlreadyStarted : Exception() | ||
| 32 | +class AlreadyStopped : Exception() | ||
| 33 | +class TorchError : Exception() | ||
| 34 | +class CameraError : Exception() | ||
| 35 | +class TorchWhenStopped : Exception() | ||
| 36 | + | ||
| 37 | +class MobileScanner( | ||
| 38 | + private val activity: Activity, | ||
| 39 | + private val textureRegistry: TextureRegistry, | ||
| 40 | + private val mobileScannerCallback: MobileScannerCallback, | ||
| 41 | + private val mobileScannerErrorCallback: MobileScannerErrorCallback | ||
| 42 | +) : | ||
| 36 | PluginRegistry.RequestPermissionsResultListener { | 43 | PluginRegistry.RequestPermissionsResultListener { |
| 37 | companion object { | 44 | companion object { |
| 38 | /** | 45 | /** |
| @@ -40,10 +47,8 @@ class MobileScanner(private val activity: Activity, private val textureRegistry: | @@ -40,10 +47,8 @@ class MobileScanner(private val activity: Activity, private val textureRegistry: | ||
| 40 | * @see androidx.fragment.app.FragmentActivity.validateRequestPermissionsRequestCode | 47 | * @see androidx.fragment.app.FragmentActivity.validateRequestPermissionsRequestCode |
| 41 | */ | 48 | */ |
| 42 | private const val REQUEST_CODE = 0x0786 | 49 | private const val REQUEST_CODE = 0x0786 |
| 43 | - private val TAG = MobileScanner::class.java.simpleName | ||
| 44 | } | 50 | } |
| 45 | 51 | ||
| 46 | - private var sink: EventChannel.EventSink? = null | ||
| 47 | private var listener: PluginRegistry.RequestPermissionsResultListener? = null | 52 | private var listener: PluginRegistry.RequestPermissionsResultListener? = null |
| 48 | 53 | ||
| 49 | private var cameraProvider: ProcessCameraProvider? = null | 54 | private var cameraProvider: ProcessCameraProvider? = null |
| @@ -51,202 +56,151 @@ class MobileScanner(private val activity: Activity, private val textureRegistry: | @@ -51,202 +56,151 @@ class MobileScanner(private val activity: Activity, private val textureRegistry: | ||
| 51 | private var preview: Preview? = null | 56 | private var preview: Preview? = null |
| 52 | private var textureEntry: TextureRegistry.SurfaceTextureEntry? = null | 57 | private var textureEntry: TextureRegistry.SurfaceTextureEntry? = null |
| 53 | 58 | ||
| 54 | -// @AnalyzeMode | ||
| 55 | -// private var analyzeMode: Int = AnalyzeMode.NONE | 59 | + private var detectionSpeed: DetectionSpeed = DetectionSpeed.NO_DUPLICATES |
| 60 | + private var detectionTimeout: Long = 250 | ||
| 61 | + private var lastScanned: List<String?>? = null | ||
| 56 | 62 | ||
| 57 | - @ExperimentalGetImage | ||
| 58 | - override fun onMethodCall(@NonNull call: MethodCall, @NonNull result: MethodChannel.Result) { | ||
| 59 | - when (call.method) { | ||
| 60 | - "state" -> checkPermission(result) | ||
| 61 | - "request" -> requestPermission(result) | ||
| 62 | - "start" -> start(call, result) | ||
| 63 | - "torch" -> toggleTorch(call, result) | ||
| 64 | -// "analyze" -> switchAnalyzeMode(call, result) | ||
| 65 | - "stop" -> stop(result) | ||
| 66 | - "analyzeImage" -> analyzeImage(call, result) | ||
| 67 | - else -> result.notImplemented() | ||
| 68 | - } | ||
| 69 | - } | 63 | + private var scannerTimeout = false |
| 70 | 64 | ||
| 71 | - override fun onListen(arguments: Any?, events: EventChannel.EventSink?) { | ||
| 72 | - this.sink = events | ||
| 73 | - } | 65 | + private var returnImage = false |
| 74 | 66 | ||
| 75 | - override fun onCancel(arguments: Any?) { | ||
| 76 | - sink = null | ||
| 77 | - } | ||
| 78 | - | ||
| 79 | - override fun onRequestPermissionsResult( | ||
| 80 | - requestCode: Int, | ||
| 81 | - permissions: Array<out String>, | ||
| 82 | - grantResults: IntArray | ||
| 83 | - ): Boolean { | ||
| 84 | - return listener?.onRequestPermissionsResult(requestCode, permissions, grantResults) ?: false | ||
| 85 | - } | 67 | + private var scanner = BarcodeScanning.getClient() |
| 86 | 68 | ||
| 87 | - private fun checkPermission(result: MethodChannel.Result) { | 69 | + /** |
| 70 | + * Check if we already have camera permission. | ||
| 71 | + */ | ||
| 72 | + fun hasCameraPermission(): Int { | ||
| 88 | // Can't get exact denied or not_determined state without request. Just return not_determined when state isn't authorized | 73 | // Can't get exact denied or not_determined state without request. Just return not_determined when state isn't authorized |
| 89 | - val state = | ||
| 90 | - if (ContextCompat.checkSelfPermission( | 74 | + val hasPermission = ContextCompat.checkSelfPermission( |
| 91 | activity, | 75 | activity, |
| 92 | Manifest.permission.CAMERA | 76 | Manifest.permission.CAMERA |
| 93 | ) == PackageManager.PERMISSION_GRANTED | 77 | ) == PackageManager.PERMISSION_GRANTED |
| 94 | - ) 1 | ||
| 95 | - else 0 | ||
| 96 | - result.success(state) | 78 | + |
| 79 | + return if (hasPermission) { | ||
| 80 | + 1 | ||
| 81 | + } else { | ||
| 82 | + 0 | ||
| 83 | + } | ||
| 97 | } | 84 | } |
| 98 | 85 | ||
| 99 | - private fun requestPermission(result: MethodChannel.Result) { | ||
| 100 | - listener = PluginRegistry.RequestPermissionsResultListener { requestCode, _, grantResults -> | 86 | + /** |
| 87 | + * Request camera permissions. | ||
| 88 | + */ | ||
| 89 | + fun requestPermission(permissionCallback: PermissionCallback) { | ||
| 90 | + listener | ||
| 91 | + ?: PluginRegistry.RequestPermissionsResultListener { requestCode, _, grantResults -> | ||
| 101 | if (requestCode != REQUEST_CODE) { | 92 | if (requestCode != REQUEST_CODE) { |
| 102 | false | 93 | false |
| 103 | } else { | 94 | } else { |
| 104 | val authorized = grantResults[0] == PackageManager.PERMISSION_GRANTED | 95 | val authorized = grantResults[0] == PackageManager.PERMISSION_GRANTED |
| 105 | - result.success(authorized) | ||
| 106 | - listener = null | 96 | + permissionCallback(authorized) |
| 107 | true | 97 | true |
| 108 | } | 98 | } |
| 109 | } | 99 | } |
| 110 | val permissions = arrayOf(Manifest.permission.CAMERA) | 100 | val permissions = arrayOf(Manifest.permission.CAMERA) |
| 111 | ActivityCompat.requestPermissions(activity, permissions, REQUEST_CODE) | 101 | ActivityCompat.requestPermissions(activity, permissions, REQUEST_CODE) |
| 112 | } | 102 | } |
| 113 | -// var lastScanned: List<Barcode>? = null | ||
| 114 | -// var isAnalyzing: Boolean = false | ||
| 115 | 103 | ||
| 116 | - @ExperimentalGetImage | ||
| 117 | - val analyzer = ImageAnalysis.Analyzer { imageProxy -> // YUV_420_888 format | 104 | + /** |
| 105 | + * Calls the callback after permissions are requested. | ||
| 106 | + */ | ||
| 107 | + override fun onRequestPermissionsResult( | ||
| 108 | + requestCode: Int, | ||
| 109 | + permissions: Array<out String>, | ||
| 110 | + grantResults: IntArray | ||
| 111 | + ): Boolean { | ||
| 112 | + return listener?.onRequestPermissionsResult(requestCode, permissions, grantResults) ?: false | ||
| 113 | + } | ||
| 118 | 114 | ||
| 115 | + /** | ||
| 116 | + * callback for the camera. Every frame is passed through this function. | ||
| 117 | + */ | ||
| 118 | + @ExperimentalGetImage | ||
| 119 | + val captureOutput = ImageAnalysis.Analyzer { imageProxy -> // YUV_420_888 format | ||
| 119 | val mediaImage = imageProxy.image ?: return@Analyzer | 120 | val mediaImage = imageProxy.image ?: return@Analyzer |
| 120 | val inputImage = InputImage.fromMediaImage(mediaImage, imageProxy.imageInfo.rotationDegrees) | 121 | val inputImage = InputImage.fromMediaImage(mediaImage, imageProxy.imageInfo.rotationDegrees) |
| 121 | 122 | ||
| 123 | + if (detectionSpeed == DetectionSpeed.NORMAL && scannerTimeout) { | ||
| 124 | + imageProxy.close() | ||
| 125 | + return@Analyzer | ||
| 126 | + } else if (detectionSpeed == DetectionSpeed.NORMAL) { | ||
| 127 | + scannerTimeout = true | ||
| 128 | + } | ||
| 129 | + | ||
| 122 | scanner.process(inputImage) | 130 | scanner.process(inputImage) |
| 123 | .addOnSuccessListener { barcodes -> | 131 | .addOnSuccessListener { barcodes -> |
| 124 | -// if (isAnalyzing) { | ||
| 125 | -// Log.d("scanner", "SKIPPING" ) | ||
| 126 | -// return@addOnSuccessListener | ||
| 127 | -// } | ||
| 128 | -// isAnalyzing = true | 132 | + if (detectionSpeed == DetectionSpeed.NO_DUPLICATES) { |
| 133 | + val newScannedBarcodes = barcodes.map { barcode -> barcode.rawValue } | ||
| 134 | + if (newScannedBarcodes == lastScanned) { | ||
| 135 | + // New scanned is duplicate, returning | ||
| 136 | + return@addOnSuccessListener | ||
| 137 | + } | ||
| 138 | + lastScanned = newScannedBarcodes | ||
| 139 | + } | ||
| 140 | + | ||
| 129 | val barcodeMap = barcodes.map { barcode -> barcode.data } | 141 | val barcodeMap = barcodes.map { barcode -> barcode.data } |
| 142 | + | ||
| 130 | if (barcodeMap.isNotEmpty()) { | 143 | if (barcodeMap.isNotEmpty()) { |
| 131 | - sink?.success(mapOf( | ||
| 132 | - "name" to "barcode", | ||
| 133 | - "data" to barcodeMap, | ||
| 134 | - "image" to mediaImage.toByteArray() | ||
| 135 | - )) | 144 | + mobileScannerCallback( |
| 145 | + barcodeMap, | ||
| 146 | + if (returnImage) mediaImage.toByteArray() else null | ||
| 147 | + ) | ||
| 136 | } | 148 | } |
| 137 | -// for (barcode in barcodes) { | ||
| 138 | -//// if (lastScanned?.contains(barcodes.first) == true) continue; | ||
| 139 | -// if (lastScanned == null) { | ||
| 140 | -// lastScanned = barcodes | ||
| 141 | -// } else if (lastScanned!!.contains(barcode)) { | ||
| 142 | -// // Duplicate, don't send image | ||
| 143 | -// sink?.success(mapOf( | ||
| 144 | -// "name" to "barcode", | ||
| 145 | -// "data" to barcode.data, | ||
| 146 | -// )) | ||
| 147 | -// } else { | ||
| 148 | -// if (byteArray.isEmpty()) { | ||
| 149 | -// Log.d("scanner", "EMPTY" ) | ||
| 150 | -// return@addOnSuccessListener | ||
| 151 | -// } | ||
| 152 | -// | ||
| 153 | -// Log.d("scanner", "SCANNED IMAGE: $byteArray") | ||
| 154 | -// lastScanned = barcodes; | ||
| 155 | -// | ||
| 156 | -// | ||
| 157 | -// } | ||
| 158 | -// | ||
| 159 | -// } | ||
| 160 | -// isAnalyzing = false | ||
| 161 | } | 149 | } |
| 162 | - .addOnFailureListener { e -> sink?.success(mapOf( | ||
| 163 | - "name" to "error", | ||
| 164 | - "data" to e.localizedMessage | ||
| 165 | - )) } | ||
| 166 | - .addOnCompleteListener { imageProxy.close() } | 150 | + .addOnFailureListener { e -> |
| 151 | + mobileScannerErrorCallback( | ||
| 152 | + e.localizedMessage ?: e.toString() | ||
| 153 | + ) | ||
| 167 | } | 154 | } |
| 155 | + .addOnCompleteListener { imageProxy.close() } | ||
| 168 | 156 | ||
| 169 | - private fun Image.toByteArray(): ByteArray { | ||
| 170 | - val yBuffer = planes[0].buffer // Y | ||
| 171 | - val vuBuffer = planes[2].buffer // VU | ||
| 172 | - | ||
| 173 | - val ySize = yBuffer.remaining() | ||
| 174 | - val vuSize = vuBuffer.remaining() | ||
| 175 | - | ||
| 176 | - val nv21 = ByteArray(ySize + vuSize) | ||
| 177 | - | ||
| 178 | - yBuffer.get(nv21, 0, ySize) | ||
| 179 | - vuBuffer.get(nv21, ySize, vuSize) | ||
| 180 | - | ||
| 181 | - val yuvImage = YuvImage(nv21, ImageFormat.NV21, this.width, this.height, null) | ||
| 182 | - val out = ByteArrayOutputStream() | ||
| 183 | - yuvImage.compressToJpeg(Rect(0, 0, yuvImage.width, yuvImage.height), 50, out) | ||
| 184 | - return out.toByteArray() | 157 | + if (detectionSpeed == DetectionSpeed.NORMAL) { |
| 158 | + // Set timer and continue | ||
| 159 | + Handler(Looper.getMainLooper()).postDelayed({ | ||
| 160 | + scannerTimeout = false | ||
| 161 | + }, detectionTimeout) | ||
| 162 | + } | ||
| 185 | } | 163 | } |
| 186 | 164 | ||
| 187 | - private var scanner = BarcodeScanning.getClient() | ||
| 188 | - | ||
| 189 | - | 165 | + /** |
| 166 | + * Start barcode scanning by initializing the camera and barcode scanner. | ||
| 167 | + */ | ||
| 190 | @ExperimentalGetImage | 168 | @ExperimentalGetImage |
| 191 | - private fun start(call: MethodCall, result: MethodChannel.Result) { | 169 | + fun start( |
| 170 | + barcodeScannerOptions: BarcodeScannerOptions?, | ||
| 171 | + returnImage: Boolean, | ||
| 172 | + cameraPosition: CameraSelector, | ||
| 173 | + torch: Boolean, | ||
| 174 | + detectionSpeed: DetectionSpeed, | ||
| 175 | + torchStateCallback: TorchStateCallback, | ||
| 176 | + mobileScannerStartedCallback: MobileScannerStartedCallback, | ||
| 177 | + detectionTimeout: Long | ||
| 178 | + ) { | ||
| 179 | + this.detectionSpeed = detectionSpeed | ||
| 180 | + this.detectionTimeout = detectionTimeout | ||
| 181 | + this.returnImage = returnImage | ||
| 182 | + | ||
| 192 | if (camera?.cameraInfo != null && preview != null && textureEntry != null) { | 183 | if (camera?.cameraInfo != null && preview != null && textureEntry != null) { |
| 193 | - val resolution = preview!!.resolutionInfo!!.resolution | ||
| 194 | - val portrait = camera!!.cameraInfo.sensorRotationDegrees % 180 == 0 | ||
| 195 | - val width = resolution.width.toDouble() | ||
| 196 | - val height = resolution.height.toDouble() | ||
| 197 | - val size = if (portrait) mapOf( | ||
| 198 | - "width" to width, | ||
| 199 | - "height" to height | ||
| 200 | - ) else mapOf("width" to height, "height" to width) | ||
| 201 | - val answer = mapOf( | ||
| 202 | - "textureId" to textureEntry!!.id(), | ||
| 203 | - "size" to size, | ||
| 204 | - "torchable" to camera!!.cameraInfo.hasFlashUnit() | ||
| 205 | - ) | ||
| 206 | - result.success(answer) | ||
| 207 | - } else { | ||
| 208 | - val facing: Int = call.argument<Int>("facing") ?: 0 | ||
| 209 | - val ratio: Int? = call.argument<Int>("ratio") | ||
| 210 | - val torch: Boolean = call.argument<Boolean>("torch") ?: false | ||
| 211 | - val formats: List<Int>? = call.argument<List<Int>>("formats") | ||
| 212 | -// val analyzerWidth = call.argument<Int>("ratio") | ||
| 213 | -// val analyzeRHEIG = call.argument<Int>("ratio") | ||
| 214 | - | ||
| 215 | - if (formats != null) { | ||
| 216 | - val formatsList: MutableList<Int> = mutableListOf() | ||
| 217 | - for (index in formats) { | ||
| 218 | - formatsList.add(BarcodeFormats.values()[index].intValue) | 184 | + throw AlreadyStarted() |
| 219 | } | 185 | } |
| 220 | - scanner = if (formatsList.size == 1) { | ||
| 221 | - BarcodeScanning.getClient( | ||
| 222 | - BarcodeScannerOptions.Builder().setBarcodeFormats(formatsList.first()) | ||
| 223 | - .build() | ||
| 224 | - ) | 186 | + |
| 187 | + scanner = if (barcodeScannerOptions != null) { | ||
| 188 | + BarcodeScanning.getClient(barcodeScannerOptions) | ||
| 225 | } else { | 189 | } else { |
| 226 | - BarcodeScanning.getClient( | ||
| 227 | - BarcodeScannerOptions.Builder().setBarcodeFormats( | ||
| 228 | - formatsList.first(), | ||
| 229 | - *formatsList.subList(1, formatsList.size).toIntArray() | ||
| 230 | - ).build() | ||
| 231 | - ) | ||
| 232 | - } | 190 | + BarcodeScanning.getClient() |
| 233 | } | 191 | } |
| 234 | 192 | ||
| 235 | - val future = ProcessCameraProvider.getInstance(activity) | 193 | + val cameraProviderFuture = ProcessCameraProvider.getInstance(activity) |
| 236 | val executor = ContextCompat.getMainExecutor(activity) | 194 | val executor = ContextCompat.getMainExecutor(activity) |
| 237 | 195 | ||
| 238 | - future.addListener({ | ||
| 239 | - cameraProvider = future.get() | 196 | + cameraProviderFuture.addListener({ |
| 197 | + cameraProvider = cameraProviderFuture.get() | ||
| 240 | if (cameraProvider == null) { | 198 | if (cameraProvider == null) { |
| 241 | - result.error("cameraProvider", "cameraProvider is null", null) | ||
| 242 | - return@addListener | 199 | + throw CameraError() |
| 243 | } | 200 | } |
| 244 | cameraProvider!!.unbindAll() | 201 | cameraProvider!!.unbindAll() |
| 245 | textureEntry = textureRegistry.createSurfaceTexture() | 202 | textureEntry = textureRegistry.createSurfaceTexture() |
| 246 | - if (textureEntry == null) { | ||
| 247 | - result.error("textureEntry", "textureEntry is null", null) | ||
| 248 | - return@addListener | ||
| 249 | - } | 203 | + |
| 250 | // Preview | 204 | // Preview |
| 251 | val surfaceProvider = Preview.SurfaceProvider { request -> | 205 | val surfaceProvider = Preview.SurfaceProvider { request -> |
| 252 | val texture = textureEntry!!.surfaceTexture() | 206 | val texture = textureEntry!!.surfaceTexture() |
| @@ -254,53 +208,39 @@ class MobileScanner(private val activity: Activity, private val textureRegistry: | @@ -254,53 +208,39 @@ class MobileScanner(private val activity: Activity, private val textureRegistry: | ||
| 254 | request.resolution.width, | 208 | request.resolution.width, |
| 255 | request.resolution.height | 209 | request.resolution.height |
| 256 | ) | 210 | ) |
| 211 | + | ||
| 257 | val surface = Surface(texture) | 212 | val surface = Surface(texture) |
| 258 | request.provideSurface(surface, executor) { } | 213 | request.provideSurface(surface, executor) { } |
| 259 | } | 214 | } |
| 260 | 215 | ||
| 261 | // Build the preview to be shown on the Flutter texture | 216 | // Build the preview to be shown on the Flutter texture |
| 262 | val previewBuilder = Preview.Builder() | 217 | val previewBuilder = Preview.Builder() |
| 263 | - if (ratio != null) { | ||
| 264 | - previewBuilder.setTargetAspectRatio(ratio) | ||
| 265 | - } | ||
| 266 | preview = previewBuilder.build().apply { setSurfaceProvider(surfaceProvider) } | 218 | preview = previewBuilder.build().apply { setSurfaceProvider(surfaceProvider) } |
| 267 | 219 | ||
| 268 | // Build the analyzer to be passed on to MLKit | 220 | // Build the analyzer to be passed on to MLKit |
| 269 | val analysisBuilder = ImageAnalysis.Builder() | 221 | val analysisBuilder = ImageAnalysis.Builder() |
| 270 | .setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST) | 222 | .setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST) |
| 271 | - if (ratio != null) { | ||
| 272 | - analysisBuilder.setTargetAspectRatio(ratio) | ||
| 273 | - } | ||
| 274 | // analysisBuilder.setTargetResolution(Size(1440, 1920)) | 223 | // analysisBuilder.setTargetResolution(Size(1440, 1920)) |
| 275 | - val analysis = analysisBuilder.build().apply { setAnalyzer(executor, analyzer) } | ||
| 276 | - | ||
| 277 | - // Select the correct camera | ||
| 278 | - val selector = | ||
| 279 | - if (facing == 0) CameraSelector.DEFAULT_FRONT_CAMERA else CameraSelector.DEFAULT_BACK_CAMERA | 224 | + val analysis = analysisBuilder.build().apply { setAnalyzer(executor, captureOutput) } |
| 280 | 225 | ||
| 281 | camera = cameraProvider!!.bindToLifecycle( | 226 | camera = cameraProvider!!.bindToLifecycle( |
| 282 | activity as LifecycleOwner, | 227 | activity as LifecycleOwner, |
| 283 | - selector, | 228 | + cameraPosition, |
| 284 | preview, | 229 | preview, |
| 285 | analysis | 230 | analysis |
| 286 | ) | 231 | ) |
| 287 | 232 | ||
| 288 | - val analysisSize = analysis.resolutionInfo?.resolution ?: Size(0, 0) | ||
| 289 | - val previewSize = preview!!.resolutionInfo?.resolution ?: Size(0, 0) | ||
| 290 | - Log.i("LOG", "Analyzer: $analysisSize") | ||
| 291 | - Log.i("LOG", "Preview: $previewSize") | ||
| 292 | - | ||
| 293 | - if (camera == null) { | ||
| 294 | - result.error("camera", "camera is null", null) | ||
| 295 | - return@addListener | ||
| 296 | - } | ||
| 297 | - | ||
| 298 | // Register the torch listener | 233 | // Register the torch listener |
| 299 | camera!!.cameraInfo.torchState.observe(activity) { state -> | 234 | camera!!.cameraInfo.torchState.observe(activity) { state -> |
| 300 | // TorchState.OFF = 0; TorchState.ON = 1 | 235 | // TorchState.OFF = 0; TorchState.ON = 1 |
| 301 | - sink?.success(mapOf("name" to "torchState", "data" to state)) | 236 | + torchStateCallback(state) |
| 302 | } | 237 | } |
| 303 | 238 | ||
| 239 | +// val analysisSize = analysis.resolutionInfo?.resolution ?: Size(0, 0) | ||
| 240 | +// val previewSize = preview!!.resolutionInfo?.resolution ?: Size(0, 0) | ||
| 241 | +// Log.i("LOG", "Analyzer: $analysisSize") | ||
| 242 | +// Log.i("LOG", "Preview: $previewSize") | ||
| 243 | + | ||
| 304 | // Enable torch if provided | 244 | // Enable torch if provided |
| 305 | camera!!.cameraControl.enableTorch(torch) | 245 | camera!!.cameraControl.enableTorch(torch) |
| 306 | 246 | ||
| @@ -308,58 +248,25 @@ class MobileScanner(private val activity: Activity, private val textureRegistry: | @@ -308,58 +248,25 @@ class MobileScanner(private val activity: Activity, private val textureRegistry: | ||
| 308 | val portrait = camera!!.cameraInfo.sensorRotationDegrees % 180 == 0 | 248 | val portrait = camera!!.cameraInfo.sensorRotationDegrees % 180 == 0 |
| 309 | val width = resolution.width.toDouble() | 249 | val width = resolution.width.toDouble() |
| 310 | val height = resolution.height.toDouble() | 250 | val height = resolution.height.toDouble() |
| 311 | - val size = if (portrait) mapOf( | ||
| 312 | - "width" to width, | ||
| 313 | - "height" to height | ||
| 314 | - ) else mapOf("width" to height, "height" to width) | ||
| 315 | - val answer = mapOf( | ||
| 316 | - "textureId" to textureEntry!!.id(), | ||
| 317 | - "size" to size, | ||
| 318 | - "torchable" to camera!!.cameraInfo.hasFlashUnit() | 251 | + |
| 252 | + mobileScannerStartedCallback( | ||
| 253 | + MobileScannerStartParameters( | ||
| 254 | + if (portrait) width else height, | ||
| 255 | + if (portrait) height else width, | ||
| 256 | + camera!!.cameraInfo.hasFlashUnit(), | ||
| 257 | + textureEntry!!.id() | ||
| 258 | + ) | ||
| 319 | ) | 259 | ) |
| 320 | - result.success(answer) | ||
| 321 | }, executor) | 260 | }, executor) |
| 322 | - } | ||
| 323 | - } | ||
| 324 | - | ||
| 325 | - private fun toggleTorch(call: MethodCall, result: MethodChannel.Result) { | ||
| 326 | - if (camera == null) { | ||
| 327 | - result.error(TAG, "Called toggleTorch() while stopped!", null) | ||
| 328 | - return | ||
| 329 | - } | ||
| 330 | - camera!!.cameraControl.enableTorch(call.arguments == 1) | ||
| 331 | - result.success(null) | ||
| 332 | - } | ||
| 333 | - | ||
| 334 | -// private fun switchAnalyzeMode(call: MethodCall, result: MethodChannel.Result) { | ||
| 335 | -// analyzeMode = call.arguments as Int | ||
| 336 | -// result.success(null) | ||
| 337 | -// } | ||
| 338 | - | ||
| 339 | - private fun analyzeImage(call: MethodCall, result: MethodChannel.Result) { | ||
| 340 | - val uri = Uri.fromFile(File(call.arguments.toString())) | ||
| 341 | - val inputImage = InputImage.fromFilePath(activity, uri) | ||
| 342 | - | ||
| 343 | - var barcodeFound = false | ||
| 344 | - scanner.process(inputImage) | ||
| 345 | - .addOnSuccessListener { barcodes -> | ||
| 346 | - for (barcode in barcodes) { | ||
| 347 | - barcodeFound = true | ||
| 348 | - sink?.success(mapOf("name" to "barcode", "data" to barcode.data)) | ||
| 349 | - } | ||
| 350 | - } | ||
| 351 | - .addOnFailureListener { e -> | ||
| 352 | - Log.e(TAG, e.message, e) | ||
| 353 | - result.error(TAG, e.message, e) | ||
| 354 | - } | ||
| 355 | - .addOnCompleteListener { result.success(barcodeFound) } | ||
| 356 | 261 | ||
| 357 | } | 262 | } |
| 358 | 263 | ||
| 359 | - private fun stop(result: MethodChannel.Result) { | 264 | + /** |
| 265 | + * Stop barcode scanning. | ||
| 266 | + */ | ||
| 267 | + fun stop() { | ||
| 360 | if (camera == null && preview == null) { | 268 | if (camera == null && preview == null) { |
| 361 | - result.error(TAG, "Called stop() while already stopped!", null) | ||
| 362 | - return | 269 | + throw AlreadyStopped() |
| 363 | } | 270 | } |
| 364 | 271 | ||
| 365 | val owner = activity as LifecycleOwner | 272 | val owner = activity as LifecycleOwner |
| @@ -367,81 +274,43 @@ class MobileScanner(private val activity: Activity, private val textureRegistry: | @@ -367,81 +274,43 @@ class MobileScanner(private val activity: Activity, private val textureRegistry: | ||
| 367 | cameraProvider?.unbindAll() | 274 | cameraProvider?.unbindAll() |
| 368 | textureEntry?.release() | 275 | textureEntry?.release() |
| 369 | 276 | ||
| 370 | -// analyzeMode = AnalyzeMode.NONE | ||
| 371 | camera = null | 277 | camera = null |
| 372 | preview = null | 278 | preview = null |
| 373 | textureEntry = null | 279 | textureEntry = null |
| 374 | cameraProvider = null | 280 | cameraProvider = null |
| 375 | - | ||
| 376 | - result.success(null) | ||
| 377 | } | 281 | } |
| 378 | 282 | ||
| 283 | + /** | ||
| 284 | + * Toggles the flash light on or off. | ||
| 285 | + */ | ||
| 286 | + fun toggleTorch(enableTorch: Boolean) { | ||
| 287 | + if (camera == null) { | ||
| 288 | + throw TorchWhenStopped() | ||
| 289 | + } | ||
| 290 | + camera!!.cameraControl.enableTorch(enableTorch) | ||
| 291 | + } | ||
| 379 | 292 | ||
| 380 | - private val Barcode.data: Map<String, Any?> | ||
| 381 | - get() = mapOf( | ||
| 382 | - "corners" to cornerPoints?.map { corner -> corner.data }, "format" to format, | ||
| 383 | - "rawBytes" to rawBytes, "rawValue" to rawValue, "type" to valueType, | ||
| 384 | - "calendarEvent" to calendarEvent?.data, "contactInfo" to contactInfo?.data, | ||
| 385 | - "driverLicense" to driverLicense?.data, "email" to email?.data, | ||
| 386 | - "geoPoint" to geoPoint?.data, "phone" to phone?.data, "sms" to sms?.data, | ||
| 387 | - "url" to url?.data, "wifi" to wifi?.data, "displayValue" to displayValue | ||
| 388 | - ) | ||
| 389 | - | ||
| 390 | - private val Point.data: Map<String, Double> | ||
| 391 | - get() = mapOf("x" to x.toDouble(), "y" to y.toDouble()) | ||
| 392 | - | ||
| 393 | - private val Barcode.CalendarEvent.data: Map<String, Any?> | ||
| 394 | - get() = mapOf( | ||
| 395 | - "description" to description, "end" to end?.rawValue, "location" to location, | ||
| 396 | - "organizer" to organizer, "start" to start?.rawValue, "status" to status, | ||
| 397 | - "summary" to summary | ||
| 398 | - ) | ||
| 399 | - | ||
| 400 | - private val Barcode.ContactInfo.data: Map<String, Any?> | ||
| 401 | - get() = mapOf( | ||
| 402 | - "addresses" to addresses.map { address -> address.data }, | ||
| 403 | - "emails" to emails.map { email -> email.data }, "name" to name?.data, | ||
| 404 | - "organization" to organization, "phones" to phones.map { phone -> phone.data }, | ||
| 405 | - "title" to title, "urls" to urls | ||
| 406 | - ) | ||
| 407 | - | ||
| 408 | - private val Barcode.Address.data: Map<String, Any?> | ||
| 409 | - get() = mapOf( | ||
| 410 | - "addressLines" to addressLines.map { addressLine -> addressLine.toString() }, | ||
| 411 | - "type" to type | ||
| 412 | - ) | 293 | + /** |
| 294 | + * Analyze a single image. | ||
| 295 | + */ | ||
| 296 | + fun analyzeImage(image: Uri, analyzerCallback: AnalyzerCallback) { | ||
| 297 | + val inputImage = InputImage.fromFilePath(activity, image) | ||
| 413 | 298 | ||
| 414 | - private val Barcode.PersonName.data: Map<String, Any?> | ||
| 415 | - get() = mapOf( | ||
| 416 | - "first" to first, "formattedName" to formattedName, "last" to last, | ||
| 417 | - "middle" to middle, "prefix" to prefix, "pronunciation" to pronunciation, | ||
| 418 | - "suffix" to suffix | ||
| 419 | - ) | 299 | + scanner.process(inputImage) |
| 300 | + .addOnSuccessListener { barcodes -> | ||
| 301 | + val barcodeMap = barcodes.map { barcode -> barcode.data } | ||
| 420 | 302 | ||
| 421 | - private val Barcode.DriverLicense.data: Map<String, Any?> | ||
| 422 | - get() = mapOf( | ||
| 423 | - "addressCity" to addressCity, "addressState" to addressState, | ||
| 424 | - "addressStreet" to addressStreet, "addressZip" to addressZip, "birthDate" to birthDate, | ||
| 425 | - "documentType" to documentType, "expiryDate" to expiryDate, "firstName" to firstName, | ||
| 426 | - "gender" to gender, "issueDate" to issueDate, "issuingCountry" to issuingCountry, | ||
| 427 | - "lastName" to lastName, "licenseNumber" to licenseNumber, "middleName" to middleName | 303 | + if (barcodeMap.isNotEmpty()) { |
| 304 | + analyzerCallback(barcodeMap) | ||
| 305 | + } else { | ||
| 306 | + analyzerCallback(null) | ||
| 307 | + } | ||
| 308 | + } | ||
| 309 | + .addOnFailureListener { e -> | ||
| 310 | + mobileScannerErrorCallback( | ||
| 311 | + e.localizedMessage ?: e.toString() | ||
| 428 | ) | 312 | ) |
| 313 | + } | ||
| 314 | + } | ||
| 429 | 315 | ||
| 430 | - private val Barcode.Email.data: Map<String, Any?> | ||
| 431 | - get() = mapOf("address" to address, "body" to body, "subject" to subject, "type" to type) | ||
| 432 | - | ||
| 433 | - private val Barcode.GeoPoint.data: Map<String, Any?> | ||
| 434 | - get() = mapOf("latitude" to lat, "longitude" to lng) | ||
| 435 | - | ||
| 436 | - private val Barcode.Phone.data: Map<String, Any?> | ||
| 437 | - get() = mapOf("number" to number, "type" to type) | ||
| 438 | - | ||
| 439 | - private val Barcode.Sms.data: Map<String, Any?> | ||
| 440 | - get() = mapOf("message" to message, "phoneNumber" to phoneNumber) | ||
| 441 | - | ||
| 442 | - private val Barcode.UrlBookmark.data: Map<String, Any?> | ||
| 443 | - get() = mapOf("title" to title, "url" to url) | ||
| 444 | - | ||
| 445 | - private val Barcode.WiFi.data: Map<String, Any?> | ||
| 446 | - get() = mapOf("encryptionType" to encryptionType, "password" to password, "ssid" to ssid) | ||
| 447 | } | 316 | } |
| 1 | package dev.steenbakker.mobile_scanner | 1 | package dev.steenbakker.mobile_scanner |
| 2 | 2 | ||
| 3 | -import androidx.annotation.NonNull | 3 | +import android.net.Uri |
| 4 | +import androidx.camera.core.CameraSelector | ||
| 5 | +import androidx.camera.core.ExperimentalGetImage | ||
| 6 | +import com.google.mlkit.vision.barcode.BarcodeScannerOptions | ||
| 7 | +import dev.steenbakker.mobile_scanner.objects.BarcodeFormats | ||
| 8 | +import dev.steenbakker.mobile_scanner.objects.DetectionSpeed | ||
| 4 | import io.flutter.embedding.engine.plugins.FlutterPlugin | 9 | import io.flutter.embedding.engine.plugins.FlutterPlugin |
| 5 | import io.flutter.embedding.engine.plugins.activity.ActivityAware | 10 | import io.flutter.embedding.engine.plugins.activity.ActivityAware |
| 6 | import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding | 11 | import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding |
| 7 | -import io.flutter.plugin.common.EventChannel | 12 | +import io.flutter.plugin.common.MethodCall |
| 8 | import io.flutter.plugin.common.MethodChannel | 13 | import io.flutter.plugin.common.MethodChannel |
| 14 | +import java.io.File | ||
| 9 | 15 | ||
| 10 | /** MobileScannerPlugin */ | 16 | /** MobileScannerPlugin */ |
| 11 | -class MobileScannerPlugin : FlutterPlugin, ActivityAware { | ||
| 12 | - private var flutter: FlutterPlugin.FlutterPluginBinding? = null | ||
| 13 | - private var activity: ActivityPluginBinding? = null | 17 | +class MobileScannerPlugin : FlutterPlugin, ActivityAware, MethodChannel.MethodCallHandler { |
| 18 | + | ||
| 19 | + private var flutterPluginBinding: FlutterPlugin.FlutterPluginBinding? = null | ||
| 20 | + private var activityPluginBinding: ActivityPluginBinding? = null | ||
| 14 | private var handler: MobileScanner? = null | 21 | private var handler: MobileScanner? = null |
| 15 | private var method: MethodChannel? = null | 22 | private var method: MethodChannel? = null |
| 16 | - private var event: EventChannel? = null | ||
| 17 | 23 | ||
| 18 | - override fun onAttachedToEngine(@NonNull binding: FlutterPlugin.FlutterPluginBinding) { | ||
| 19 | - this.flutter = binding | 24 | + private lateinit var barcodeHandler: BarcodeHandler |
| 25 | + | ||
| 26 | + private var permissionResult: MethodChannel.Result? = null | ||
| 27 | + private var analyzerResult: MethodChannel.Result? = null | ||
| 28 | + | ||
| 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? -> | ||
| 35 | + if (image != null) { | ||
| 36 | + barcodeHandler.publishEvent(mapOf( | ||
| 37 | + "name" to "barcode", | ||
| 38 | + "data" to barcodes, | ||
| 39 | + "image" to image | ||
| 40 | + )) | ||
| 41 | + } else { | ||
| 42 | + barcodeHandler.publishEvent(mapOf( | ||
| 43 | + "name" to "barcode", | ||
| 44 | + "data" to barcodes | ||
| 45 | + )) | ||
| 46 | + } | ||
| 47 | + } | ||
| 48 | + | ||
| 49 | + private val analyzerCallback: AnalyzerCallback = { barcodes: List<Map<String, Any?>>?-> | ||
| 50 | + if (barcodes != null) { | ||
| 51 | + barcodeHandler.publishEvent(mapOf( | ||
| 52 | + "name" to "barcode", | ||
| 53 | + "data" to barcodes | ||
| 54 | + )) | ||
| 55 | + analyzerResult?.success(true) | ||
| 56 | + } else { | ||
| 57 | + analyzerResult?.success(false) | ||
| 58 | + } | ||
| 59 | + analyzerResult = null | ||
| 60 | + } | ||
| 61 | + | ||
| 62 | + private val errorCallback: MobileScannerErrorCallback = {error: String -> | ||
| 63 | + barcodeHandler.publishEvent(mapOf( | ||
| 64 | + "name" to "error", | ||
| 65 | + "data" to error, | ||
| 66 | + )) | ||
| 20 | } | 67 | } |
| 21 | 68 | ||
| 22 | - override fun onDetachedFromEngine(@NonNull binding: FlutterPlugin.FlutterPluginBinding) { | ||
| 23 | - this.flutter = null | 69 | + private val torchStateCallback: TorchStateCallback = {state: Int -> |
| 70 | + barcodeHandler.publishEvent(mapOf("name" to "torchState", "data" to state)) | ||
| 24 | } | 71 | } |
| 25 | 72 | ||
| 26 | - override fun onAttachedToActivity(binding: ActivityPluginBinding) { | ||
| 27 | - activity = binding | ||
| 28 | - handler = MobileScanner(activity!!.activity, flutter!!.textureRegistry) | ||
| 29 | - method = MethodChannel(flutter!!.binaryMessenger, "dev.steenbakker.mobile_scanner/scanner/method") | ||
| 30 | - event = EventChannel(flutter!!.binaryMessenger, "dev.steenbakker.mobile_scanner/scanner/event") | ||
| 31 | - method!!.setMethodCallHandler(handler) | ||
| 32 | - event!!.setStreamHandler(handler) | ||
| 33 | - activity!!.addRequestPermissionsResultListener(handler!!) | 73 | + @ExperimentalGetImage |
| 74 | + override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) { | ||
| 75 | + if (handler == null) { | ||
| 76 | + result.error("MobileScanner", "Called ${call.method} before initializing.", null) | ||
| 77 | + return | ||
| 78 | + } | ||
| 79 | + when (call.method) { | ||
| 80 | + "state" -> result.success(handler!!.hasCameraPermission()) | ||
| 81 | + "request" -> requestPermission(result) | ||
| 82 | + "start" -> start(call, result) | ||
| 83 | + "torch" -> toggleTorch(call, result) | ||
| 84 | + "stop" -> stop(result) | ||
| 85 | + "analyzeImage" -> analyzeImage(call, result) | ||
| 86 | + else -> result.notImplemented() | ||
| 87 | + } | ||
| 88 | + } | ||
| 89 | + | ||
| 90 | + override fun onAttachedToEngine(binding: FlutterPlugin.FlutterPluginBinding) { | ||
| 91 | + method = MethodChannel(binding.binaryMessenger, "dev.steenbakker.mobile_scanner/scanner/method") | ||
| 92 | + method!!.setMethodCallHandler(this) | ||
| 93 | + | ||
| 94 | + barcodeHandler = BarcodeHandler(binding) | ||
| 95 | + | ||
| 96 | + this.flutterPluginBinding = binding | ||
| 97 | + } | ||
| 98 | + | ||
| 99 | + override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) { | ||
| 100 | + this.flutterPluginBinding = null | ||
| 101 | + } | ||
| 102 | + | ||
| 103 | + override fun onAttachedToActivity(activityPluginBinding: ActivityPluginBinding) { | ||
| 104 | + handler = MobileScanner(activityPluginBinding.activity, flutterPluginBinding!!.textureRegistry, callback, errorCallback | ||
| 105 | + ) | ||
| 106 | + activityPluginBinding.addRequestPermissionsResultListener(handler!!) | ||
| 107 | + | ||
| 108 | + this.activityPluginBinding = activityPluginBinding | ||
| 34 | } | 109 | } |
| 35 | 110 | ||
| 36 | override fun onReattachedToActivityForConfigChanges(binding: ActivityPluginBinding) { | 111 | override fun onReattachedToActivityForConfigChanges(binding: ActivityPluginBinding) { |
| @@ -38,16 +113,117 @@ class MobileScannerPlugin : FlutterPlugin, ActivityAware { | @@ -38,16 +113,117 @@ class MobileScannerPlugin : FlutterPlugin, ActivityAware { | ||
| 38 | } | 113 | } |
| 39 | 114 | ||
| 40 | override fun onDetachedFromActivity() { | 115 | override fun onDetachedFromActivity() { |
| 41 | - activity!!.removeRequestPermissionsResultListener(handler!!) | ||
| 42 | - event!!.setStreamHandler(null) | 116 | + activityPluginBinding!!.removeRequestPermissionsResultListener(handler!!) |
| 43 | method!!.setMethodCallHandler(null) | 117 | method!!.setMethodCallHandler(null) |
| 44 | - event = null | ||
| 45 | method = null | 118 | method = null |
| 46 | handler = null | 119 | handler = null |
| 47 | - activity = null | 120 | + activityPluginBinding = null |
| 48 | } | 121 | } |
| 49 | 122 | ||
| 50 | override fun onDetachedFromActivityForConfigChanges() { | 123 | override fun onDetachedFromActivityForConfigChanges() { |
| 51 | onDetachedFromActivity() | 124 | onDetachedFromActivity() |
| 52 | } | 125 | } |
| 126 | + | ||
| 127 | + private fun requestPermission(result: MethodChannel.Result) { | ||
| 128 | + permissionResult = result | ||
| 129 | + handler!!.requestPermission(permissionCallback) | ||
| 130 | + } | ||
| 131 | + | ||
| 132 | + @ExperimentalGetImage | ||
| 133 | + private fun start(call: MethodCall, result: MethodChannel.Result) { | ||
| 134 | + val torch: Boolean = call.argument<Boolean>("torch") ?: false | ||
| 135 | + val facing: Int = call.argument<Int>("facing") ?: 0 | ||
| 136 | + val formats: List<Int>? = call.argument<List<Int>>("formats") | ||
| 137 | + val returnImage: Boolean = call.argument<Boolean>("returnImage") ?: false | ||
| 138 | + val speed: Int = call.argument<Int>("speed") ?: 1 | ||
| 139 | + val timeout: Int = call.argument<Int>("timeout") ?: 250 | ||
| 140 | + | ||
| 141 | + var barcodeScannerOptions: BarcodeScannerOptions? = null | ||
| 142 | + if (formats != null) { | ||
| 143 | + val formatsList: MutableList<Int> = mutableListOf() | ||
| 144 | + for (index in formats) { | ||
| 145 | + formatsList.add(BarcodeFormats.values()[index].intValue) | ||
| 146 | + } | ||
| 147 | + barcodeScannerOptions = if (formatsList.size == 1) { | ||
| 148 | + BarcodeScannerOptions.Builder().setBarcodeFormats(formatsList.first()) | ||
| 149 | + .build() | ||
| 150 | + } else { | ||
| 151 | + BarcodeScannerOptions.Builder().setBarcodeFormats( | ||
| 152 | + formatsList.first(), | ||
| 153 | + *formatsList.subList(1, formatsList.size).toIntArray() | ||
| 154 | + ).build() | ||
| 155 | + } | ||
| 156 | + } | ||
| 157 | + | ||
| 158 | + val position = | ||
| 159 | + if (facing == 0) CameraSelector.DEFAULT_FRONT_CAMERA else CameraSelector.DEFAULT_BACK_CAMERA | ||
| 160 | + | ||
| 161 | + val detectionSpeed: DetectionSpeed = DetectionSpeed.values().first { it.intValue == speed} | ||
| 162 | + | ||
| 163 | + try { | ||
| 164 | + handler!!.start(barcodeScannerOptions, returnImage, position, torch, detectionSpeed, torchStateCallback, mobileScannerStartedCallback = { | ||
| 165 | + result.success(mapOf( | ||
| 166 | + "textureId" to it.id, | ||
| 167 | + "size" to mapOf("width" to it.width, "height" to it.height), | ||
| 168 | + "torchable" to it.hasFlashUnit | ||
| 169 | + )) | ||
| 170 | + }, | ||
| 171 | + timeout.toLong()) | ||
| 172 | + | ||
| 173 | + } catch (e: AlreadyStarted) { | ||
| 174 | + result.error( | ||
| 175 | + "MobileScanner", | ||
| 176 | + "Called start() while already started", | ||
| 177 | + null | ||
| 178 | + ) | ||
| 179 | + } catch (e: NoCamera) { | ||
| 180 | + result.error( | ||
| 181 | + "MobileScanner", | ||
| 182 | + "No camera found or failed to open camera!", | ||
| 183 | + null | ||
| 184 | + ) | ||
| 185 | + } catch (e: TorchError) { | ||
| 186 | + result.error( | ||
| 187 | + "MobileScanner", | ||
| 188 | + "Error occurred when setting torch!", | ||
| 189 | + null | ||
| 190 | + ) | ||
| 191 | + } catch (e: CameraError) { | ||
| 192 | + result.error( | ||
| 193 | + "MobileScanner", | ||
| 194 | + "Error occurred when setting up camera!", | ||
| 195 | + null | ||
| 196 | + ) | ||
| 197 | + } catch (e: Exception) { | ||
| 198 | + result.error( | ||
| 199 | + "MobileScanner", | ||
| 200 | + "Unknown error occurred..", | ||
| 201 | + null | ||
| 202 | + ) | ||
| 203 | + } | ||
| 204 | + } | ||
| 205 | + | ||
| 206 | + private fun stop(result: MethodChannel.Result) { | ||
| 207 | + try { | ||
| 208 | + handler!!.stop() | ||
| 209 | + result.success(null) | ||
| 210 | + } catch (e: AlreadyStopped) { | ||
| 211 | + result.error("MobileScanner", "Called stop() while already stopped!", null) | ||
| 212 | + } | ||
| 213 | + } | ||
| 214 | + | ||
| 215 | + private fun analyzeImage(call: MethodCall, result: MethodChannel.Result) { | ||
| 216 | + analyzerResult = result | ||
| 217 | + val uri = Uri.fromFile(File(call.arguments.toString())) | ||
| 218 | + handler!!.analyzeImage(uri, analyzerCallback) | ||
| 219 | + } | ||
| 220 | + | ||
| 221 | + private fun toggleTorch(call: MethodCall, result: MethodChannel.Result) { | ||
| 222 | + try { | ||
| 223 | + handler!!.toggleTorch(call.arguments == 1) | ||
| 224 | + result.success(null) | ||
| 225 | + } catch (e: AlreadyStopped) { | ||
| 226 | + result.error("MobileScanner", "Called toggleTorch() while stopped!", null) | ||
| 227 | + } | ||
| 228 | + } | ||
| 53 | } | 229 | } |
| 1 | +package dev.steenbakker.mobile_scanner | ||
| 2 | + | ||
| 3 | +import android.graphics.ImageFormat | ||
| 4 | +import android.graphics.Point | ||
| 5 | +import android.graphics.Rect | ||
| 6 | +import android.graphics.YuvImage | ||
| 7 | +import android.media.Image | ||
| 8 | +import com.google.mlkit.vision.barcode.common.Barcode | ||
| 9 | +import java.io.ByteArrayOutputStream | ||
| 10 | + | ||
| 11 | +fun Image.toByteArray(): ByteArray { | ||
| 12 | + val yBuffer = planes[0].buffer // Y | ||
| 13 | + val vuBuffer = planes[2].buffer // VU | ||
| 14 | + | ||
| 15 | + val ySize = yBuffer.remaining() | ||
| 16 | + val vuSize = vuBuffer.remaining() | ||
| 17 | + | ||
| 18 | + val nv21 = ByteArray(ySize + vuSize) | ||
| 19 | + | ||
| 20 | + yBuffer.get(nv21, 0, ySize) | ||
| 21 | + vuBuffer.get(nv21, ySize, vuSize) | ||
| 22 | + | ||
| 23 | + val yuvImage = YuvImage(nv21, ImageFormat.NV21, this.width, this.height, null) | ||
| 24 | + val out = ByteArrayOutputStream() | ||
| 25 | + yuvImage.compressToJpeg(Rect(0, 0, yuvImage.width, yuvImage.height), 50, out) | ||
| 26 | + return out.toByteArray() | ||
| 27 | +} | ||
| 28 | + | ||
| 29 | +val Barcode.data: Map<String, Any?> | ||
| 30 | + get() = mapOf( | ||
| 31 | + "corners" to cornerPoints?.map { corner -> corner.data }, "format" to format, | ||
| 32 | + "rawBytes" to rawBytes, "rawValue" to rawValue, "type" to valueType, | ||
| 33 | + "calendarEvent" to calendarEvent?.data, "contactInfo" to contactInfo?.data, | ||
| 34 | + "driverLicense" to driverLicense?.data, "email" to email?.data, | ||
| 35 | + "geoPoint" to geoPoint?.data, "phone" to phone?.data, "sms" to sms?.data, | ||
| 36 | + "url" to url?.data, "wifi" to wifi?.data, "displayValue" to displayValue | ||
| 37 | + ) | ||
| 38 | + | ||
| 39 | +private val Point.data: Map<String, Double> | ||
| 40 | + get() = mapOf("x" to x.toDouble(), "y" to y.toDouble()) | ||
| 41 | + | ||
| 42 | +private val Barcode.CalendarEvent.data: Map<String, Any?> | ||
| 43 | + get() = mapOf( | ||
| 44 | + "description" to description, "end" to end?.rawValue, "location" to location, | ||
| 45 | + "organizer" to organizer, "start" to start?.rawValue, "status" to status, | ||
| 46 | + "summary" to summary | ||
| 47 | + ) | ||
| 48 | + | ||
| 49 | +private val Barcode.ContactInfo.data: Map<String, Any?> | ||
| 50 | + get() = mapOf( | ||
| 51 | + "addresses" to addresses.map { address -> address.data }, | ||
| 52 | + "emails" to emails.map { email -> email.data }, "name" to name?.data, | ||
| 53 | + "organization" to organization, "phones" to phones.map { phone -> phone.data }, | ||
| 54 | + "title" to title, "urls" to urls | ||
| 55 | + ) | ||
| 56 | + | ||
| 57 | +private val Barcode.Address.data: Map<String, Any?> | ||
| 58 | + get() = mapOf( | ||
| 59 | + "addressLines" to addressLines.map { addressLine -> addressLine.toString() }, | ||
| 60 | + "type" to type | ||
| 61 | + ) | ||
| 62 | + | ||
| 63 | +private val Barcode.PersonName.data: Map<String, Any?> | ||
| 64 | + get() = mapOf( | ||
| 65 | + "first" to first, "formattedName" to formattedName, "last" to last, | ||
| 66 | + "middle" to middle, "prefix" to prefix, "pronunciation" to pronunciation, | ||
| 67 | + "suffix" to suffix | ||
| 68 | + ) | ||
| 69 | + | ||
| 70 | +private val Barcode.DriverLicense.data: Map<String, Any?> | ||
| 71 | + get() = mapOf( | ||
| 72 | + "addressCity" to addressCity, "addressState" to addressState, | ||
| 73 | + "addressStreet" to addressStreet, "addressZip" to addressZip, "birthDate" to birthDate, | ||
| 74 | + "documentType" to documentType, "expiryDate" to expiryDate, "firstName" to firstName, | ||
| 75 | + "gender" to gender, "issueDate" to issueDate, "issuingCountry" to issuingCountry, | ||
| 76 | + "lastName" to lastName, "licenseNumber" to licenseNumber, "middleName" to middleName | ||
| 77 | + ) | ||
| 78 | + | ||
| 79 | +private val Barcode.Email.data: Map<String, Any?> | ||
| 80 | + get() = mapOf("address" to address, "body" to body, "subject" to subject, "type" to type) | ||
| 81 | + | ||
| 82 | +private val Barcode.GeoPoint.data: Map<String, Any?> | ||
| 83 | + get() = mapOf("latitude" to lat, "longitude" to lng) | ||
| 84 | + | ||
| 85 | +private val Barcode.Phone.data: Map<String, Any?> | ||
| 86 | + get() = mapOf("number" to number, "type" to type) | ||
| 87 | + | ||
| 88 | +private val Barcode.Sms.data: Map<String, Any?> | ||
| 89 | + get() = mapOf("message" to message, "phoneNumber" to phoneNumber) | ||
| 90 | + | ||
| 91 | +private val Barcode.UrlBookmark.data: Map<String, Any?> | ||
| 92 | + get() = mapOf("title" to title, "url" to url) | ||
| 93 | + | ||
| 94 | +private val Barcode.WiFi.data: Map<String, Any?> | ||
| 95 | + get() = mapOf("encryptionType" to encryptionType, "password" to password, "ssid" to ssid) |
android/src/main/kotlin/dev/steenbakker/mobile_scanner/exceptions/NoPermissionException.kt
deleted
100644 → 0
| 1 | -package dev.steenbakker.mobile_scanner.exceptions | ||
| 2 | - | ||
| 3 | -internal class NoPermissionException : RuntimeException() | ||
| 4 | - | ||
| 5 | -//internal class Exception(val reason: Reason) : | ||
| 6 | -// java.lang.Exception("Mobile Scanner failed because $reason") { | ||
| 7 | -// | ||
| 8 | -// internal enum class Reason { | ||
| 9 | -// noHardware, noPermissions, noBackCamera | ||
| 10 | -// } | ||
| 11 | -//} |
| 1 | -package dev.steenbakker.mobile_scanner | ||
| 2 | - | ||
| 3 | -import com.google.mlkit.vision.barcode.BarcodeScannerOptions | 1 | +package dev.steenbakker.mobile_scanner.objects |
| 4 | 2 | ||
| 5 | enum class BarcodeFormats(val intValue: Int) { | 3 | enum class BarcodeFormats(val intValue: Int) { |
| 6 | UNKNOWN(com.google.mlkit.vision.barcode.common.Barcode.FORMAT_UNKNOWN), | 4 | UNKNOWN(com.google.mlkit.vision.barcode.common.Barcode.FORMAT_UNKNOWN), |
| @@ -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, |
| @@ -99,7 +107,7 @@ class _BarcodeListScannerWithControllerState | @@ -99,7 +107,7 @@ class _BarcodeListScannerWithControllerState | ||
| 99 | height: 50, | 107 | height: 50, |
| 100 | child: FittedBox( | 108 | child: FittedBox( |
| 101 | child: Text( | 109 | child: Text( |
| 102 | - '${barcodeCapture?.barcodes.map((e) => e.rawValue)}', | 110 | + '${barcodeCapture?.barcodes.map((e) => e.rawValue) ?? 'Scan something!'}', |
| 103 | overflow: TextOverflow.fade, | 111 | overflow: TextOverflow.fade, |
| 104 | style: Theme.of(context) | 112 | style: Theme.of(context) |
| 105 | .textTheme | 113 | .textTheme |
| @@ -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,15 +34,10 @@ class _BarcodeScannerReturningImageState | @@ -29,15 +34,10 @@ 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( | 37 | + body: SafeArea( |
| 38 | + child: Column( | ||
| 36 | children: [ | 39 | children: [ |
| 37 | - Container( | ||
| 38 | - color: Colors.blueGrey, | ||
| 39 | - width: double.infinity, | ||
| 40 | - height: 0.33 * MediaQuery.of(context).size.height, | 40 | + Expanded( |
| 41 | child: barcode?.image != null | 41 | child: barcode?.image != null |
| 42 | ? Transform.rotate( | 42 | ? Transform.rotate( |
| 43 | angle: 90 * pi / 180, | 43 | angle: 90 * pi / 180, |
| @@ -47,31 +47,27 @@ class _BarcodeScannerReturningImageState | @@ -47,31 +47,27 @@ class _BarcodeScannerReturningImageState | ||
| 47 | fit: BoxFit.contain, | 47 | fit: BoxFit.contain, |
| 48 | ), | 48 | ), |
| 49 | ) | 49 | ) |
| 50 | - : const ColoredBox( | ||
| 51 | - color: Colors.white, | ||
| 52 | - child: Center( | 50 | + : const Center( |
| 53 | child: Text( | 51 | child: Text( |
| 54 | 'Your scanned barcode will appear here!', | 52 | 'Your scanned barcode will appear here!', |
| 55 | ), | 53 | ), |
| 56 | ), | 54 | ), |
| 57 | ), | 55 | ), |
| 58 | - ), | ||
| 59 | - Container( | ||
| 60 | - height: 0.66 * MediaQuery.of(context).size.height, | 56 | + Expanded( |
| 57 | + flex: 2, | ||
| 58 | + child: ColoredBox( | ||
| 61 | color: Colors.grey, | 59 | color: Colors.grey, |
| 62 | child: Stack( | 60 | child: Stack( |
| 63 | children: [ | 61 | children: [ |
| 64 | MobileScanner( | 62 | MobileScanner( |
| 65 | controller: controller, | 63 | controller: controller, |
| 66 | fit: BoxFit.contain, | 64 | fit: BoxFit.contain, |
| 67 | - // allowDuplicates: true, | ||
| 68 | // controller: MobileScannerController( | 65 | // controller: MobileScannerController( |
| 69 | // torchEnabled: true, | 66 | // torchEnabled: true, |
| 70 | // facing: CameraFacing.front, | 67 | // facing: CameraFacing.front, |
| 71 | // ), | 68 | // ), |
| 72 | - onDetect: (barcode, arguments) { | 69 | + onDetect: (barcode) { |
| 73 | setState(() { | 70 | setState(() { |
| 74 | - this.arguments = arguments; | ||
| 75 | this.barcode = barcode; | 71 | this.barcode = barcode; |
| 76 | }); | 72 | }); |
| 77 | }, | 73 | }, |
| @@ -85,12 +81,8 @@ class _BarcodeScannerReturningImageState | @@ -85,12 +81,8 @@ class _BarcodeScannerReturningImageState | ||
| 85 | child: Row( | 81 | child: Row( |
| 86 | mainAxisAlignment: MainAxisAlignment.spaceEvenly, | 82 | mainAxisAlignment: MainAxisAlignment.spaceEvenly, |
| 87 | children: [ | 83 | children: [ |
| 88 | - ColoredBox( | ||
| 89 | - color: arguments != null && !arguments!.hasTorch | ||
| 90 | - ? Colors.red | ||
| 91 | - : Colors.white, | ||
| 92 | - child: IconButton( | ||
| 93 | - // color: , | 84 | + IconButton( |
| 85 | + color: Colors.white, | ||
| 94 | icon: ValueListenableBuilder( | 86 | icon: ValueListenableBuilder( |
| 95 | valueListenable: controller.torchState, | 87 | valueListenable: controller.torchState, |
| 96 | builder: (context, state, child) { | 88 | builder: (context, state, child) { |
| @@ -117,7 +109,6 @@ class _BarcodeScannerReturningImageState | @@ -117,7 +109,6 @@ class _BarcodeScannerReturningImageState | ||
| 117 | iconSize: 32.0, | 109 | iconSize: 32.0, |
| 118 | onPressed: () => controller.toggleTorch(), | 110 | onPressed: () => controller.toggleTorch(), |
| 119 | ), | 111 | ), |
| 120 | - ), | ||
| 121 | IconButton( | 112 | IconButton( |
| 122 | color: Colors.white, | 113 | color: Colors.white, |
| 123 | icon: isStarted | 114 | icon: isStarted |
| @@ -174,9 +165,9 @@ class _BarcodeScannerReturningImageState | @@ -174,9 +165,9 @@ class _BarcodeScannerReturningImageState | ||
| 174 | ], | 165 | ], |
| 175 | ), | 166 | ), |
| 176 | ), | 167 | ), |
| 168 | + ), | ||
| 177 | ], | 169 | ], |
| 178 | - ); | ||
| 179 | - }, | 170 | + ), |
| 180 | ), | 171 | ), |
| 181 | ); | 172 | ); |
| 182 | } | 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 | }); |
ios/Classes/DetectionSpeed.swift
0 → 100644
| @@ -49,7 +49,7 @@ public class MobileScanner: NSObject, AVCaptureVideoDataOutputSampleBufferDelega | @@ -49,7 +49,7 @@ public class MobileScanner: NSObject, AVCaptureVideoDataOutputSampleBufferDelega | ||
| 49 | super.init() | 49 | super.init() |
| 50 | } | 50 | } |
| 51 | 51 | ||
| 52 | - /// Check permissions for video | 52 | + /// Check if we already have camera permission. |
| 53 | func checkPermission() -> Int { | 53 | func checkPermission() -> Int { |
| 54 | let status = AVCaptureDevice.authorizationStatus(for: .video) | 54 | let status = AVCaptureDevice.authorizationStatus(for: .video) |
| 55 | switch status { | 55 | switch status { |
| @@ -67,6 +67,44 @@ public class MobileScanner: NSObject, AVCaptureVideoDataOutputSampleBufferDelega | @@ -67,6 +67,44 @@ public class MobileScanner: NSObject, AVCaptureVideoDataOutputSampleBufferDelega | ||
| 67 | AVCaptureDevice.requestAccess(for: .video, completionHandler: { result($0) }) | 67 | AVCaptureDevice.requestAccess(for: .video, completionHandler: { result($0) }) |
| 68 | } | 68 | } |
| 69 | 69 | ||
| 70 | + /// Gets called when a new image is added to the buffer | ||
| 71 | + public func captureOutput(_ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) { | ||
| 72 | + guard let imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) else { | ||
| 73 | + print("Failed to get image buffer from sample buffer.") | ||
| 74 | + return | ||
| 75 | + } | ||
| 76 | + latestBuffer = imageBuffer | ||
| 77 | + registry?.textureFrameAvailable(textureId) | ||
| 78 | + if ((detectionSpeed == DetectionSpeed.normal || detectionSpeed == DetectionSpeed.noDuplicates) && i > 10 || detectionSpeed == DetectionSpeed.unrestricted) { | ||
| 79 | + i = 0 | ||
| 80 | + let ciImage = latestBuffer.image | ||
| 81 | + | ||
| 82 | + let image = VisionImage(image: ciImage) | ||
| 83 | + image.orientation = imageOrientation( | ||
| 84 | + deviceOrientation: UIDevice.current.orientation, | ||
| 85 | + defaultOrientation: .portrait, | ||
| 86 | + position: videoPosition | ||
| 87 | + ) | ||
| 88 | + | ||
| 89 | + scanner.process(image) { [self] barcodes, error in | ||
| 90 | + if (detectionSpeed == DetectionSpeed.noDuplicates) { | ||
| 91 | + let newScannedBarcodes = barcodes?.map { barcode in | ||
| 92 | + return barcode.rawValue | ||
| 93 | + } | ||
| 94 | + if (error == nil && barcodesString != nil && newScannedBarcodes != nil && barcodesString!.elementsEqual(newScannedBarcodes!)) { | ||
| 95 | + return | ||
| 96 | + } else { | ||
| 97 | + barcodesString = newScannedBarcodes | ||
| 98 | + } | ||
| 99 | + } | ||
| 100 | + | ||
| 101 | + mobileScannerCallback(barcodes, error, ciImage) | ||
| 102 | + } | ||
| 103 | + } else { | ||
| 104 | + i+=1 | ||
| 105 | + } | ||
| 106 | + } | ||
| 107 | + | ||
| 70 | /// Start scanning for barcodes | 108 | /// Start scanning for barcodes |
| 71 | func start(barcodeScannerOptions: BarcodeScannerOptions?, returnImage: Bool, cameraPosition: AVCaptureDevice.Position, torch: AVCaptureDevice.TorchMode, detectionSpeed: DetectionSpeed) throws -> MobileScannerStartParameters { | 109 | func start(barcodeScannerOptions: BarcodeScannerOptions?, returnImage: Bool, cameraPosition: AVCaptureDevice.Position, torch: AVCaptureDevice.TorchMode, detectionSpeed: DetectionSpeed) throws -> MobileScannerStartParameters { |
| 72 | self.detectionSpeed = detectionSpeed | 110 | self.detectionSpeed = detectionSpeed |
| @@ -136,13 +174,6 @@ public class MobileScanner: NSObject, AVCaptureVideoDataOutputSampleBufferDelega | @@ -136,13 +174,6 @@ public class MobileScanner: NSObject, AVCaptureVideoDataOutputSampleBufferDelega | ||
| 136 | return MobileScannerStartParameters(width: Double(dimensions.height), height: Double(dimensions.width), hasTorch: device.hasTorch, textureId: textureId) | 174 | return MobileScannerStartParameters(width: Double(dimensions.height), height: Double(dimensions.width), hasTorch: device.hasTorch, textureId: textureId) |
| 137 | } | 175 | } |
| 138 | 176 | ||
| 139 | - struct MobileScannerStartParameters { | ||
| 140 | - var width: Double = 0.0 | ||
| 141 | - var height: Double = 0.0 | ||
| 142 | - var hasTorch = false | ||
| 143 | - var textureId: Int64 = 0 | ||
| 144 | - } | ||
| 145 | - | ||
| 146 | /// Stop scanning for barcodes | 177 | /// Stop scanning for barcodes |
| 147 | func stop() throws { | 178 | func stop() throws { |
| 148 | if (device == nil) { | 179 | if (device == nil) { |
| @@ -192,54 +223,18 @@ public class MobileScanner: NSObject, AVCaptureVideoDataOutputSampleBufferDelega | @@ -192,54 +223,18 @@ public class MobileScanner: NSObject, AVCaptureVideoDataOutputSampleBufferDelega | ||
| 192 | 223 | ||
| 193 | var barcodesString: Array<String?>? | 224 | var barcodesString: Array<String?>? |
| 194 | 225 | ||
| 195 | - /// Gets called when a new image is added to the buffer | ||
| 196 | - public func captureOutput(_ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) { | ||
| 197 | - guard let imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) else { | ||
| 198 | - print("Failed to get image buffer from sample buffer.") | ||
| 199 | - return | ||
| 200 | - } | ||
| 201 | - latestBuffer = imageBuffer | ||
| 202 | - registry?.textureFrameAvailable(textureId) | ||
| 203 | - if ((detectionSpeed == DetectionSpeed.normal || detectionSpeed == DetectionSpeed.noDuplicates) && i > 10 || detectionSpeed == DetectionSpeed.unrestricted) { | ||
| 204 | - i = 0 | ||
| 205 | - let ciImage = latestBuffer.image | ||
| 206 | - | ||
| 207 | - let image = VisionImage(image: ciImage) | ||
| 208 | - image.orientation = imageOrientation( | ||
| 209 | - deviceOrientation: UIDevice.current.orientation, | ||
| 210 | - defaultOrientation: .portrait, | ||
| 211 | - position: videoPosition | ||
| 212 | - ) | ||
| 213 | - | ||
| 214 | - scanner.process(image) { [self] barcodes, error in | ||
| 215 | - if (detectionSpeed == DetectionSpeed.noDuplicates) { | ||
| 216 | - let newScannedBarcodes = barcodes?.map { barcode in | ||
| 217 | - return barcode.rawValue | ||
| 218 | - } | ||
| 219 | - if (error == nil && barcodesString != nil && newScannedBarcodes != nil && barcodesString!.elementsEqual(newScannedBarcodes!)) { | ||
| 220 | - return | ||
| 221 | - } else { | ||
| 222 | - barcodesString = newScannedBarcodes | ||
| 223 | - } | ||
| 224 | - } | ||
| 225 | - | ||
| 226 | - mobileScannerCallback(barcodes, error, ciImage) | ||
| 227 | - } | ||
| 228 | - } else { | ||
| 229 | - i+=1 | ||
| 230 | - } | ||
| 231 | - } | ||
| 232 | - | ||
| 233 | - /// Convert image buffer to jpeg | ||
| 234 | - private func ciImageToJpeg(ciImage: CIImage) -> Data { | ||
| 235 | 226 | ||
| 236 | - // let ciImage = CIImage(cvPixelBuffer: latestBuffer) | ||
| 237 | - let context:CIContext = CIContext.init(options: nil) | ||
| 238 | - let cgImage:CGImage = context.createCGImage(ciImage, from: ciImage.extent)! | ||
| 239 | - let uiImage:UIImage = UIImage(cgImage: cgImage, scale: 1, orientation: UIImage.Orientation.up) | ||
| 240 | 227 | ||
| 241 | - return uiImage.jpegData(compressionQuality: 0.8)!; | ||
| 242 | - } | 228 | +// /// Convert image buffer to jpeg |
| 229 | +// private func ciImageToJpeg(ciImage: CIImage) -> Data { | ||
| 230 | +// | ||
| 231 | +// // let ciImage = CIImage(cvPixelBuffer: latestBuffer) | ||
| 232 | +// let context:CIContext = CIContext.init(options: nil) | ||
| 233 | +// let cgImage:CGImage = context.createCGImage(ciImage, from: ciImage.extent)! | ||
| 234 | +// let uiImage:UIImage = UIImage(cgImage: cgImage, scale: 1, orientation: UIImage.Orientation.up) | ||
| 235 | +// | ||
| 236 | +// return uiImage.jpegData(compressionQuality: 0.8)!; | ||
| 237 | +// } | ||
| 243 | 238 | ||
| 244 | /// Rotates images accordingly | 239 | /// Rotates images accordingly |
| 245 | func imageOrientation( | 240 | func imageOrientation( |
| @@ -271,5 +266,11 @@ public class MobileScanner: NSObject, AVCaptureVideoDataOutputSampleBufferDelega | @@ -271,5 +266,11 @@ public class MobileScanner: NSObject, AVCaptureVideoDataOutputSampleBufferDelega | ||
| 271 | return Unmanaged<CVPixelBuffer>.passRetained(latestBuffer) | 266 | return Unmanaged<CVPixelBuffer>.passRetained(latestBuffer) |
| 272 | } | 267 | } |
| 273 | 268 | ||
| 269 | + struct MobileScannerStartParameters { | ||
| 270 | + var width: Double = 0.0 | ||
| 271 | + var height: Double = 0.0 | ||
| 272 | + var hasTorch = false | ||
| 273 | + var textureId: Int64 = 0 | ||
| 274 | + } | ||
| 274 | } | 275 | } |
| 275 | 276 |
| @@ -21,7 +21,7 @@ extension CVBuffer { | @@ -21,7 +21,7 @@ extension CVBuffer { | ||
| 21 | var image: UIImage { | 21 | var image: UIImage { |
| 22 | let ciImage = CIImage(cvPixelBuffer: self) | 22 | let ciImage = CIImage(cvPixelBuffer: self) |
| 23 | let cgImage = CIContext().createCGImage(ciImage, from: ciImage.extent) | 23 | let cgImage = CIContext().createCGImage(ciImage, from: ciImage.extent) |
| 24 | - return UIImage(cgImage: cgImage!) | 24 | + return UIImage(cgImage: cgImage!, scale: 1.0, orientation: UIImage.Orientation.left) |
| 25 | } | 25 | } |
| 26 | 26 | ||
| 27 | var image1: UIImage { | 27 | var image1: UIImage { |
| @@ -56,11 +56,11 @@ public class SwiftMobileScannerPlugin: NSObject, FlutterPlugin { | @@ -56,11 +56,11 @@ public class SwiftMobileScannerPlugin: NSObject, FlutterPlugin { | ||
| 56 | 56 | ||
| 57 | /// Parses all parameters and starts the mobileScanner | 57 | /// Parses all parameters and starts the mobileScanner |
| 58 | private func start(_ call: FlutterMethodCall, _ result: @escaping FlutterResult) { | 58 | private func start(_ call: FlutterMethodCall, _ result: @escaping FlutterResult) { |
| 59 | - // let ratio: Int = (call.arguments as! Dictionary<String, Any?>)["ratio"] as! Int | ||
| 60 | let torch: Bool = (call.arguments as! Dictionary<String, Any?>)["torch"] as? Bool ?? false | 59 | let torch: Bool = (call.arguments as! Dictionary<String, Any?>)["torch"] as? Bool ?? false |
| 61 | let facing: Int = (call.arguments as! Dictionary<String, Any?>)["facing"] as? Int ?? 1 | 60 | let facing: Int = (call.arguments as! Dictionary<String, Any?>)["facing"] as? Int ?? 1 |
| 62 | let formats: Array<Int> = (call.arguments as! Dictionary<String, Any?>)["formats"] as? Array ?? [] | 61 | let formats: Array<Int> = (call.arguments as! Dictionary<String, Any?>)["formats"] as? Array ?? [] |
| 63 | let returnImage: Bool = (call.arguments as! Dictionary<String, Any?>)["returnImage"] as? Bool ?? false | 62 | let returnImage: Bool = (call.arguments as! Dictionary<String, Any?>)["returnImage"] as? Bool ?? false |
| 63 | + let speed: Int = (call.arguments as! Dictionary<String, Any?>)["speed"] as? Int ?? 0 | ||
| 64 | 64 | ||
| 65 | let formatList = formats.map { format in return BarcodeFormat(rawValue: format)} | 65 | let formatList = formats.map { format in return BarcodeFormat(rawValue: format)} |
| 66 | var barcodeOptions: BarcodeScannerOptions? = nil | 66 | var barcodeOptions: BarcodeScannerOptions? = nil |
| @@ -75,10 +75,10 @@ public class SwiftMobileScannerPlugin: NSObject, FlutterPlugin { | @@ -75,10 +75,10 @@ public class SwiftMobileScannerPlugin: NSObject, FlutterPlugin { | ||
| 75 | 75 | ||
| 76 | 76 | ||
| 77 | let position = facing == 0 ? AVCaptureDevice.Position.front : .back | 77 | let position = facing == 0 ? AVCaptureDevice.Position.front : .back |
| 78 | - let speed: DetectionSpeed = DetectionSpeed(rawValue: (call.arguments as! Dictionary<String, Any?>)["speed"] as? Int ?? 0)! | 78 | + let detectionSpeed: DetectionSpeed = DetectionSpeed(rawValue: speed)! |
| 79 | 79 | ||
| 80 | do { | 80 | do { |
| 81 | - let parameters = try mobileScanner.start(barcodeScannerOptions: barcodeOptions, returnImage: returnImage, cameraPosition: position, torch: torch ? .on : .off, detectionSpeed: speed) | 81 | + let parameters = try mobileScanner.start(barcodeScannerOptions: barcodeOptions, returnImage: returnImage, cameraPosition: position, torch: torch ? .on : .off, detectionSpeed: detectionSpeed) |
| 82 | result(["textureId": parameters.textureId, "size": ["width": parameters.width, "height": parameters.height], "torchable": parameters.hasTorch]) | 82 | result(["textureId": parameters.textureId, "size": ["width": parameters.width, "height": parameters.height], "torchable": parameters.hasTorch]) |
| 83 | } catch MobileScannerError.alreadyStarted { | 83 | } catch MobileScannerError.alreadyStarted { |
| 84 | result(FlutterError(code: "MobileScanner", | 84 | result(FlutterError(code: "MobileScanner", |
| @@ -90,7 +90,7 @@ public class SwiftMobileScannerPlugin: NSObject, FlutterPlugin { | @@ -90,7 +90,7 @@ public class SwiftMobileScannerPlugin: NSObject, FlutterPlugin { | ||
| 90 | details: nil)) | 90 | details: nil)) |
| 91 | } catch MobileScannerError.torchError(let error) { | 91 | } catch MobileScannerError.torchError(let error) { |
| 92 | result(FlutterError(code: "MobileScanner", | 92 | result(FlutterError(code: "MobileScanner", |
| 93 | - message: "Error occured when setting toch!", | 93 | + message: "Error occured when setting torch!", |
| 94 | details: error)) | 94 | details: error)) |
| 95 | } catch MobileScannerError.cameraError(let error) { | 95 | } catch MobileScannerError.cameraError(let error) { |
| 96 | result(FlutterError(code: "MobileScanner", | 96 | result(FlutterError(code: "MobileScanner", |
| @@ -162,9 +162,3 @@ public class SwiftMobileScannerPlugin: NSObject, FlutterPlugin { | @@ -162,9 +162,3 @@ public class SwiftMobileScannerPlugin: NSObject, FlutterPlugin { | ||
| 162 | } | 162 | } |
| 163 | } | 163 | } |
| 164 | } | 164 | } |
| 165 | - | ||
| 166 | -enum DetectionSpeed: Int { | ||
| 167 | - case noDuplicates = 0 | ||
| 168 | - case normal = 1 | ||
| 169 | - case unrestricted = 2 | ||
| 170 | -} |
| 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 | - /// Front facing camera. | 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,9 +41,11 @@ class MobileScanner extends StatefulWidget { | @@ -29,9 +41,11 @@ 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, |
| 48 | + @Deprecated('Use the [onPermissionSet] paremeter in the [MobileScannerController] instead.') | ||
| 35 | this.onPermissionSet, | 49 | this.onPermissionSet, |
| 36 | }); | 50 | }); |
| 37 | 51 | ||
| @@ -49,27 +63,39 @@ class _MobileScannerState extends State<MobileScanner> | @@ -49,27 +63,39 @@ 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 | + } | ||
| 53 | } | 69 | } |
| 54 | 70 | ||
| 55 | - AppLifecycleState? _lastState; | 71 | + Future<void> _startScanner() async { |
| 72 | + final arguments = await controller.start(); | ||
| 73 | + widget.onStart?.call(arguments); | ||
| 74 | + } | ||
| 75 | + | ||
| 76 | + bool resumeFromBackground = false; | ||
| 56 | 77 | ||
| 57 | @override | 78 | @override |
| 58 | void didChangeAppLifecycleState(AppLifecycleState state) { | 79 | void didChangeAppLifecycleState(AppLifecycleState state) { |
| 80 | + // App state changed before it is initialized. | ||
| 81 | + if (controller.isStarting) { | ||
| 82 | + return; | ||
| 83 | + } | ||
| 84 | + | ||
| 59 | switch (state) { | 85 | switch (state) { |
| 60 | case AppLifecycleState.resumed: | 86 | case AppLifecycleState.resumed: |
| 61 | - if (!controller.isStarting && | ||
| 62 | - widget.autoResume && | ||
| 63 | - _lastState != AppLifecycleState.inactive) controller.start(); | 87 | + resumeFromBackground = false; |
| 88 | + _startScanner(); | ||
| 64 | break; | 89 | break; |
| 65 | case AppLifecycleState.paused: | 90 | case AppLifecycleState.paused: |
| 66 | - case AppLifecycleState.detached: | ||
| 67 | - controller.stop(); | 91 | + resumeFromBackground = true; |
| 92 | + break; | ||
| 93 | + case AppLifecycleState.inactive: | ||
| 94 | + if (!resumeFromBackground) controller.stop(); | ||
| 68 | break; | 95 | break; |
| 69 | default: | 96 | default: |
| 70 | break; | 97 | break; |
| 71 | } | 98 | } |
| 72 | - _lastState = state; | ||
| 73 | } | 99 | } |
| 74 | 100 | ||
| 75 | @override | 101 | @override |
| @@ -82,7 +108,7 @@ class _MobileScannerState extends State<MobileScanner> | @@ -82,7 +108,7 @@ class _MobileScannerState extends State<MobileScanner> | ||
| 82 | return const ColoredBox(color: Colors.black); | 108 | return const ColoredBox(color: Colors.black); |
| 83 | } else { | 109 | } else { |
| 84 | controller.barcodes.listen((barcode) { | 110 | controller.barcodes.listen((barcode) { |
| 85 | - widget.onDetect(barcode, value! as MobileScannerArguments); | 111 | + widget.onDetect(barcode); |
| 86 | }); | 112 | }); |
| 87 | return ClipRect( | 113 | return ClipRect( |
| 88 | child: SizedBox( | 114 | child: SizedBox( |
| @@ -13,11 +13,10 @@ import 'package:mobile_scanner/src/mobile_scanner_exception.dart'; | @@ -13,11 +13,10 @@ import 'package:mobile_scanner/src/mobile_scanner_exception.dart'; | ||
| 13 | class MobileScannerController { | 13 | class MobileScannerController { |
| 14 | MobileScannerController({ | 14 | MobileScannerController({ |
| 15 | this.facing = CameraFacing.back, | 15 | this.facing = CameraFacing.back, |
| 16 | - this.detectionSpeed = DetectionSpeed.noDuplicates, | ||
| 17 | - // this.ratio, | 16 | + this.detectionSpeed = DetectionSpeed.normal, |
| 17 | + this.detectionTimeoutMs = 250, | ||
| 18 | this.torchEnabled = false, | 18 | this.torchEnabled = false, |
| 19 | this.formats, | 19 | this.formats, |
| 20 | - // this.autoResume = true, | ||
| 21 | this.returnImage = false, | 20 | this.returnImage = false, |
| 22 | this.onPermissionSet, | 21 | this.onPermissionSet, |
| 23 | }) { | 22 | }) { |
| @@ -31,7 +30,8 @@ class MobileScannerController { | @@ -31,7 +30,8 @@ class MobileScannerController { | ||
| 31 | .listen((data) => _handleEvent(data as Map)); | 30 | .listen((data) => _handleEvent(data as Map)); |
| 32 | } | 31 | } |
| 33 | 32 | ||
| 34 | - //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 | ||
| 35 | static int? controllerHashcode; | 35 | static int? controllerHashcode; |
| 36 | 36 | ||
| 37 | /// Select which camera should be used. | 37 | /// Select which camera should be used. |
| @@ -39,11 +39,6 @@ class MobileScannerController { | @@ -39,11 +39,6 @@ class MobileScannerController { | ||
| 39 | /// Default: CameraFacing.back | 39 | /// Default: CameraFacing.back |
| 40 | final CameraFacing facing; | 40 | final CameraFacing facing; |
| 41 | 41 | ||
| 42 | - // /// Analyze the image in 4:3 or 16:9 | ||
| 43 | - // /// | ||
| 44 | - // /// Only on Android | ||
| 45 | - // final Ratio? ratio; | ||
| 46 | - | ||
| 47 | /// Enable or disable the torch (Flash) on start | 42 | /// Enable or disable the torch (Flash) on start |
| 48 | /// | 43 | /// |
| 49 | /// Default: disabled | 44 | /// Default: disabled |
| @@ -62,6 +57,13 @@ class MobileScannerController { | @@ -62,6 +57,13 @@ class MobileScannerController { | ||
| 62 | /// WARNING: DetectionSpeed.unrestricted can cause memory issues on some devices | 57 | /// WARNING: DetectionSpeed.unrestricted can cause memory issues on some devices |
| 63 | final DetectionSpeed detectionSpeed; | 58 | final DetectionSpeed detectionSpeed; |
| 64 | 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). | ||
| 65 | + final int detectionTimeoutMs; | ||
| 66 | + | ||
| 65 | /// Sets the barcode stream | 67 | /// Sets the barcode stream |
| 66 | final StreamController<BarcodeCapture> _barcodesController = | 68 | final StreamController<BarcodeCapture> _barcodesController = |
| 67 | StreamController.broadcast(); | 69 | StreamController.broadcast(); |
| @@ -89,6 +91,7 @@ class MobileScannerController { | @@ -89,6 +91,7 @@ class MobileScannerController { | ||
| 89 | ValueNotifier(facing); | 91 | ValueNotifier(facing); |
| 90 | 92 | ||
| 91 | bool isStarting = false; | 93 | bool isStarting = false; |
| 94 | + | ||
| 92 | bool? _hasTorch; | 95 | bool? _hasTorch; |
| 93 | 96 | ||
| 94 | /// Set the starting arguments for the camera | 97 | /// Set the starting arguments for the camera |
| @@ -97,10 +100,9 @@ class MobileScannerController { | @@ -97,10 +100,9 @@ class MobileScannerController { | ||
| 97 | 100 | ||
| 98 | cameraFacingState.value = cameraFacingOverride ?? facing; | 101 | cameraFacingState.value = cameraFacingOverride ?? facing; |
| 99 | arguments['facing'] = cameraFacingState.value.index; | 102 | arguments['facing'] = cameraFacingState.value.index; |
| 100 | - | ||
| 101 | - // if (ratio != null) arguments['ratio'] = ratio; | ||
| 102 | arguments['torch'] = torchEnabled; | 103 | arguments['torch'] = torchEnabled; |
| 103 | arguments['speed'] = detectionSpeed.index; | 104 | arguments['speed'] = detectionSpeed.index; |
| 105 | + arguments['timeout'] = detectionTimeoutMs; | ||
| 104 | 106 | ||
| 105 | if (formats != null) { | 107 | if (formats != null) { |
| 106 | if (Platform.isAndroid) { | 108 | if (Platform.isAndroid) { |
| @@ -118,9 +120,9 @@ class MobileScannerController { | @@ -118,9 +120,9 @@ class MobileScannerController { | ||
| 118 | Future<MobileScannerArguments?> start({ | 120 | Future<MobileScannerArguments?> start({ |
| 119 | CameraFacing? cameraFacingOverride, | 121 | CameraFacing? cameraFacingOverride, |
| 120 | }) async { | 122 | }) async { |
| 121 | - debugPrint('Hashcode controller: $hashCode'); | ||
| 122 | if (isStarting) { | 123 | if (isStarting) { |
| 123 | debugPrint("Called start() while starting."); | 124 | debugPrint("Called start() while starting."); |
| 125 | + return null; | ||
| 124 | } | 126 | } |
| 125 | isStarting = true; | 127 | isStarting = true; |
| 126 | 128 | ||
| @@ -157,10 +159,10 @@ class MobileScannerController { | @@ -157,10 +159,10 @@ class MobileScannerController { | ||
| 157 | ); | 159 | ); |
| 158 | } on PlatformException catch (error) { | 160 | } on PlatformException catch (error) { |
| 159 | debugPrint('${error.code}: ${error.message}'); | 161 | debugPrint('${error.code}: ${error.message}'); |
| 160 | - isStarting = false; | ||
| 161 | if (error.code == "MobileScannerWeb") { | 162 | if (error.code == "MobileScannerWeb") { |
| 162 | onPermissionSet?.call(false); | 163 | onPermissionSet?.call(false); |
| 163 | } | 164 | } |
| 165 | + isStarting = false; | ||
| 164 | return null; | 166 | return null; |
| 165 | } | 167 | } |
| 166 | 168 | ||
| @@ -177,32 +179,33 @@ class MobileScannerController { | @@ -177,32 +179,33 @@ class MobileScannerController { | ||
| 177 | } | 179 | } |
| 178 | 180 | ||
| 179 | if (kIsWeb) { | 181 | if (kIsWeb) { |
| 182 | + // If we reach this line, it means camera permission has been granted | ||
| 180 | onPermissionSet?.call( | 183 | onPermissionSet?.call( |
| 181 | true, | 184 | true, |
| 182 | - ); // If we reach this line, it means camera permission has been granted | 185 | + ); |
| 186 | + } | ||
| 183 | 187 | ||
| 184 | - startArguments.value = MobileScannerArguments( | ||
| 185 | - webId: startResult['ViewID'] as String?, | ||
| 186 | - size: Size( | 188 | + isStarting = false; |
| 189 | + return startArguments.value = MobileScannerArguments( | ||
| 190 | + size: kIsWeb | ||
| 191 | + ? Size( | ||
| 187 | startResult['videoWidth'] as double? ?? 0, | 192 | startResult['videoWidth'] as double? ?? 0, |
| 188 | startResult['videoHeight'] as double? ?? 0, | 193 | startResult['videoHeight'] as double? ?? 0, |
| 189 | - ), | ||
| 190 | - hasTorch: _hasTorch!, | ||
| 191 | - ); | ||
| 192 | - } else { | ||
| 193 | - startArguments.value = MobileScannerArguments( | ||
| 194 | - textureId: startResult['textureId'] as int?, | ||
| 195 | - size: toSize(startResult['size'] as Map? ?? {}), | 194 | + ) |
| 195 | + : toSize(startResult['size'] as Map? ?? {}), | ||
| 196 | hasTorch: _hasTorch!, | 196 | hasTorch: _hasTorch!, |
| 197 | + textureId: kIsWeb ? null : startResult['textureId'] as int?, | ||
| 198 | + webId: kIsWeb ? startResult['ViewID'] as String? : null, | ||
| 197 | ); | 199 | ); |
| 198 | } | 200 | } |
| 199 | - isStarting = false; | ||
| 200 | - return startArguments.value!; | ||
| 201 | - } | ||
| 202 | 201 | ||
| 203 | /// Stops the camera, but does not dispose this controller. | 202 | /// Stops the camera, but does not dispose this controller. |
| 204 | Future<void> stop() async { | 203 | Future<void> stop() async { |
| 204 | + try { | ||
| 205 | await _methodChannel.invokeMethod('stop'); | 205 | await _methodChannel.invokeMethod('stop'); |
| 206 | + } catch (e) { | ||
| 207 | + debugPrint('$e'); | ||
| 208 | + } | ||
| 206 | } | 209 | } |
| 207 | 210 | ||
| 208 | /// Switches the torch on or off. | 211 | /// Switches the torch on or off. |
| @@ -277,7 +280,7 @@ class MobileScannerController { | @@ -277,7 +280,7 @@ class MobileScannerController { | ||
| 277 | _barcodesController.add( | 280 | _barcodesController.add( |
| 278 | BarcodeCapture( | 281 | BarcodeCapture( |
| 279 | barcodes: parsed, | 282 | barcodes: parsed, |
| 280 | - image: event['image'] as Uint8List, | 283 | + image: event['image'] as Uint8List?, |
| 281 | ), | 284 | ), |
| 282 | ); | 285 | ); |
| 283 | break; | 286 | break; |
| @@ -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