Julian Steenbakker

Merge remote-tracking branch 'origin/master'

Showing 100 changed files with 1926 additions and 1056 deletions

Too many changes to show.

To preserve performance only 100 of 100+ files are displayed.

@@ -12,4 +12,4 @@ jobs: @@ -12,4 +12,4 @@ jobs:
12 assign-author: 12 assign-author:
13 runs-on: ubuntu-latest 13 runs-on: ubuntu-latest
14 steps: 14 steps:
15 - - uses: toshimaru/auto-author-assign@v1.6.2 15 + - uses: toshimaru/auto-author-assign@v2.0.1
@@ -11,12 +11,12 @@ jobs: @@ -11,12 +11,12 @@ jobs:
11 analysis: 11 analysis:
12 runs-on: ubuntu-latest 12 runs-on: ubuntu-latest
13 steps: 13 steps:
14 - - uses: actions/checkout@v4.0.0  
15 - - uses: actions/setup-java@v3.12.0 14 + - uses: actions/checkout@v4.1.1
  15 + - uses: actions/setup-java@v3.13.0
16 with: 16 with:
17 - java-version: 11 17 + java-version: 17
18 distribution: temurin 18 distribution: temurin
19 - - uses: subosito/flutter-action@v2.10.0 19 + - uses: subosito/flutter-action@v2.12.0
20 with: 20 with:
21 cache: true 21 cache: true
22 - name: Version 22 - name: Version
@@ -28,12 +28,12 @@ jobs: @@ -28,12 +28,12 @@ jobs:
28 formatting: 28 formatting:
29 runs-on: ubuntu-latest 29 runs-on: ubuntu-latest
30 steps: 30 steps:
31 - - uses: actions/checkout@v4.0.0  
32 - - uses: actions/setup-java@v3.12.0 31 + - uses: actions/checkout@v4.1.1
  32 + - uses: actions/setup-java@v3.13.0
33 with: 33 with:
34 - java-version: 11 34 + java-version: 17
35 distribution: temurin 35 distribution: temurin
36 - - uses: subosito/flutter-action@v2.10.0 36 + - uses: subosito/flutter-action@v2.12.0
37 with: 37 with:
38 cache: true 38 cache: true
39 - name: Format 39 - name: Format
@@ -7,7 +7,7 @@ @@ -7,7 +7,7 @@
7 release-please: 7 release-please:
8 runs-on: ubuntu-latest 8 runs-on: ubuntu-latest
9 steps: 9 steps:
10 - - uses: GoogleCloudPlatform/release-please-action@v3.7.11 10 + - uses: GoogleCloudPlatform/release-please-action@v3.7.13
11 with: 11 with:
12 token: ${{ secrets.GITHUB_TOKEN }} 12 token: ${{ secrets.GITHUB_TOKEN }}
13 release-type: simple 13 release-type: simple
  1 +## 3.5.5
  2 +Bugs fixed:
  3 +* Fixed a bug where the scanner would get stuck after denying permissions on Android. (thanks @navaronbracke !)
  4 +
  5 +## 3.5.4
  6 +Bugs fixed:
  7 +* Fixed a bug with an implicit conversion to integer for the scan timeout for iOS. (thanks @EArminjon !)
  8 +
  9 +## 3.5.2
  10 +Improvements:
  11 +* Updated to `play-services-mlkit-barcode-scanning` version 18.3.0
  12 +
  13 +Bugs fixed:
  14 +* Fixed the `updateScanWindow()` function not completing on Android and MacOS. (thanks @navaronbracke !)
  15 +* Fixed some camera access issues, when the camera could have been null on Android. (thanks @navaronbracke !)
  16 +* Fixed a crash on Android when there is no camera. (thanks @navaronbracke !)
  17 +* Fixed a bug with the `noDuplicates` detection speed. (thanks @pgeof !)
  18 +* Fixed a synchronization issue for the torch state. (thanks @navaronbracke !)
  19 +
  20 +## 3.5.1
  21 +Improvements:
  22 +* The `type` of an `Address` is now non-null.
  23 +* The `type` of an `Email` is now non-null.
  24 +* The `phoneNumber` of an `SMS` is now non-null.
  25 +* The `latitude` and `longitude` of a `GeoPoint` are now non-null.
  26 +* The `phones` and `urls` of `ContactInfo` are now non-null.
  27 +* The `url` of a `UrlBookmark` is now non-null.
  28 +* The `type` of `Phone` is now non-null.
  29 +* The `width` and `height` of `BarcodeCapture` are now non-null.
  30 +* The `BarcodeCapture` class now exposes a `size`.
  31 +* The list of `corners` of a `Barcode` is now non-null.
  32 +
  33 +Bugs fixed:
  34 +* Fixed the default values for the `format` and `type` arguments of the Barcode constructor.
  35 + These now use `BarcodeFormat.unknown` and `BarcodeType.unknown`, rather than `BarcodeFormat.ean13` and `BarcodeType.text`.
  36 + (thanks @navaronbracke !)
  37 +* Fixed messages not being sent on the main thread for Android, iOS and MacOS. (thanks @navaronbracke !)
  38 +
  39 +## 3.5.0
  40 +New Features:
  41 +* Added the option to switch between bundled and unbundled MLKit for Android. (thanks @woolfred !)
  42 +* Added the option to specify the camera resolution for Android. (thanks @EArminjon !)
  43 +* Added a sample with a scanner overlay. (thanks @Spyy004 !)
  44 +
  45 +Bugs fixed:
  46 +* Fixed the scan window calculation taking into account the widget coordinates, instead of the screen coordinates. (thanks @jlin5 !)
  47 +* Fixed the scan window calculation returning wrong results. (thanks @MBulli !)
  48 +* Fixed the BarcodeCapture format on MacOS. (thanks @ryanduffyne !)
  49 +* Fixed the timeout for scanning on MacOS. (thanks @ryanduffyne !)
  50 +* Fixed Android builds failing by downgrading from Kotlin 1.9.10 to 1.7.22. (thanks @vbuberen !)
  51 +* Fixed images on iOS being rotated, resulting in bad detection rates. (thanks @EArminjon !)
  52 +* Fixed scan timeout not working on iOS. (thanks @navaronbracke !)
  53 +* Fixed a crash on iOS when the device is nil. (thanks @navaronbracke !)
  54 +* Fixed a case of an unhandled exception when starting the scanner. (thanks @navaronbracke !)
  55 +
  56 +Improvements:
  57 +* Improved MacOS memory footprint by using a background queue. (thanks @ryanduffyne !)
  58 +
1 ## 3.4.1 59 ## 3.4.1
2 -Change MediaQuery.sizeOf(context) to of(context).size for backwards compatibility 60 +* Changed MediaQuery.sizeOf(context) to of(context).size for compatibility with older Flutter versions.
3 61
4 ## 3.4.0 62 ## 3.4.0
5 New Features: 63 New Features:
@@ -27,11 +27,17 @@ See the example app for detailed implementation information. @@ -27,11 +27,17 @@ See the example app for detailed implementation information.
27 27
28 ## Platform specific setup 28 ## Platform specific setup
29 ### Android 29 ### Android
30 -This packages uses the **bundled version** of MLKit Barcode-scanning for Android. This version is more accurate and immediately available to devices. However, this version will increase the size of the app with approximately 3 to 10 MB. The alternative for this is to use the **unbundled version** of MLKit Barcode-scanning for Android. This version is older than the bundled version however this only increases the size by around 600KB.  
31 -To use this version you must alter the mobile_scanner gradle file to replace `com.google.mlkit:barcode-scanning:17.0.2` with `com.google.android.gms:play-services-mlkit-barcode-scanning:18.0.0`. Keep in mind that if you alter the gradle files directly in your project it can be overriden when you update your pubspec.yaml. I am still searching for a way to properly replace the module in gradle but have yet to find one. 30 +This package uses by default the **bundled version** of MLKit Barcode-scanning for Android. This version is immediately available to the device. But it will increase the size of the app by approximately 3 to 10 MB.
  31 +
  32 +The alternative is to use the **unbundled version** of MLKit Barcode-scanning for Android. This version is downloaded on first use via Google Play Services. It increases the app size by around 600KB.
32 33
33 [You can read more about the difference between the two versions here.](https://developers.google.com/ml-kit/vision/barcode-scanning/android) 34 [You can read more about the difference between the two versions here.](https://developers.google.com/ml-kit/vision/barcode-scanning/android)
34 35
  36 +To use the **unbundled version** of the MLKit Barcode-scanning, add the following line to your `/android/gradle.properties` file:
  37 +```
  38 +dev.steenbakker.mobile_scanner.useUnbundled=true
  39 +```
  40 +
35 ### iOS 41 ### iOS
36 **Add the following keys to your Info.plist file, located in <project root>/ios/Runner/Info.plist:** 42 **Add the following keys to your Info.plist file, located in <project root>/ios/Runner/Info.plist:**
37 NSCameraUsageDescription - describe why your app needs access to the camera. This is called Privacy - Camera Usage Description in the visual editor. 43 NSCameraUsageDescription - describe why your app needs access to the camera. This is called Privacy - Camera Usage Description in the visual editor.
@@ -6,3 +6,4 @@ @@ -6,3 +6,4 @@
6 .DS_Store 6 .DS_Store
7 /build 7 /build
8 /captures 8 /captures
  9 +.cxx
@@ -2,19 +2,19 @@ group 'dev.steenbakker.mobile_scanner' @@ -2,19 +2,19 @@ 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.9.10' 5 + ext.kotlin_version = '1.7.22'
6 repositories { 6 repositories {
7 google() 7 google()
8 mavenCentral() 8 mavenCentral()
9 } 9 }
10 10
11 dependencies { 11 dependencies {
12 - classpath 'com.android.tools.build:gradle:8.1.1' 12 + classpath 'com.android.tools.build:gradle:8.2.0'
13 classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 13 classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
14 } 14 }
15 } 15 }
16 16
17 -rootProject.allprojects { 17 +allprojects {
18 repositories { 18 repositories {
19 google() 19 google()
20 mavenCentral() 20 mavenCentral()
@@ -25,37 +25,60 @@ apply plugin: 'com.android.library' @@ -25,37 +25,60 @@ apply plugin: 'com.android.library'
25 apply plugin: 'kotlin-android' 25 apply plugin: 'kotlin-android'
26 26
27 android { 27 android {
28 - compileSdkVersion 33 28 + if (project.android.hasProperty("namespace")) {
  29 + namespace 'dev.steenbakker.mobile_scanner'
  30 + }
  31 +
  32 + compileSdk 34
29 33
30 compileOptions { 34 compileOptions {
31 - sourceCompatibility JavaVersion.VERSION_1_8  
32 - targetCompatibility JavaVersion.VERSION_1_8 35 + sourceCompatibility JavaVersion.VERSION_17
  36 + targetCompatibility JavaVersion.VERSION_17
33 } 37 }
34 38
35 kotlinOptions { 39 kotlinOptions {
36 - jvmTarget = '1.8' 40 + jvmTarget = '17'
37 } 41 }
38 42
39 sourceSets { 43 sourceSets {
40 main.java.srcDirs += 'src/main/kotlin' 44 main.java.srcDirs += 'src/main/kotlin'
  45 + test.java.srcDirs += 'src/test/kotlin'
41 } 46 }
42 47
43 defaultConfig { 48 defaultConfig {
44 minSdkVersion 21 49 minSdkVersion 21
  50 + consumerProguardFiles 'proguard-rules.pro'
45 } 51 }
46 52
47 - namespace 'dev.steenbakker.mobile_scanner' 53 + testOptions {
  54 + unitTests.all {
  55 + useJUnitPlatform()
  56 +
  57 + testLogging {
  58 + events "passed", "skipped", "failed", "standardOut", "standardError"
  59 + outputs.upToDateWhen {false}
  60 + showStandardStreams = true
  61 + }
  62 + }
  63 + }
48 } 64 }
49 65
50 dependencies { 66 dependencies {
51 implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 67 implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
52 68
53 - // Use this dependency to bundle the model with your app 69 + def useUnbundled = project.findProperty('dev.steenbakker.mobile_scanner.useUnbundled') ?: false
  70 + if (useUnbundled.toBoolean()) {
  71 + // Dynamically downloaded model via Google Play Services
  72 + implementation 'com.google.android.gms:play-services-mlkit-barcode-scanning:18.3.0'
  73 + } else {
  74 + // Bundled model in app
54 implementation 'com.google.mlkit:barcode-scanning:17.2.0' 75 implementation 'com.google.mlkit:barcode-scanning:17.2.0'
55 - // Use this dependency to use the dynamically downloaded model in Google Play Services  
56 -// implementation 'com.google.android.gms:play-services-mlkit-barcode-scanning:18.1.0' 76 + }
  77 +
  78 + implementation 'androidx.camera:camera-camera2:1.3.0'
  79 + implementation 'androidx.camera:camera-lifecycle:1.3.0'
  80 + implementation 'androidx.camera:camera-camera2:1.3.0'
57 81
58 - implementation 'androidx.camera:camera-camera2:1.2.3'  
59 - implementation 'androidx.camera:camera-lifecycle:1.2.3'  
60 - implementation 'androidx.camera:camera-camera2:1.2.3' 82 + testImplementation 'org.jetbrains.kotlin:kotlin-test'
  83 + testImplementation 'org.mockito:mockito-core:5.8.0'
61 } 84 }
1 -#Fri Jun 23 08:50:38 CEST 2017  
2 -distributionBase=GRADLE_USER_HOME  
3 -distributionPath=wrapper/dists  
4 -zipStoreBase=GRADLE_USER_HOME  
5 -zipStorePath=wrapper/dists  
6 -distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-all.zip  
  1 +-keep class com.google.mlkit.* { *; }
  2 +-keep class com.google.android.libraries.barhopper.** { *; }
  3 +-keep class com.google.photos.* { *; }
@@ -2,4 +2,5 @@ @@ -2,4 +2,5 @@
2 package="dev.steenbakker.mobile_scanner"> 2 package="dev.steenbakker.mobile_scanner">
3 3
4 <uses-permission android:name="android.permission.CAMERA" /> 4 <uses-permission android:name="android.permission.CAMERA" />
  5 + <uses-feature android:name="android.hardware.camera" android:required="false" />
5 </manifest> 6 </manifest>
1 package dev.steenbakker.mobile_scanner 1 package dev.steenbakker.mobile_scanner
2 2
3 import android.app.Activity 3 import android.app.Activity
  4 +import android.content.Context
4 import android.graphics.Bitmap 5 import android.graphics.Bitmap
5 import android.graphics.Matrix 6 import android.graphics.Matrix
6 import android.graphics.Rect 7 import android.graphics.Rect
  8 +import android.hardware.display.DisplayManager
7 import android.net.Uri 9 import android.net.Uri
  10 +import android.os.Build
8 import android.os.Handler 11 import android.os.Handler
9 import android.os.Looper 12 import android.os.Looper
  13 +import android.util.Size
10 import android.view.Surface 14 import android.view.Surface
11 -import androidx.camera.core.* 15 +import android.view.WindowManager
  16 +import androidx.camera.core.Camera
  17 +import androidx.camera.core.CameraSelector
  18 +import androidx.camera.core.ExperimentalGetImage
  19 +import androidx.camera.core.ImageAnalysis
  20 +import androidx.camera.core.ImageProxy
  21 +import androidx.camera.core.Preview
  22 +import androidx.camera.core.resolutionselector.AspectRatioStrategy
  23 +import androidx.camera.core.resolutionselector.ResolutionSelector
  24 +import androidx.camera.core.resolutionselector.ResolutionStrategy
12 import androidx.camera.lifecycle.ProcessCameraProvider 25 import androidx.camera.lifecycle.ProcessCameraProvider
13 import androidx.core.content.ContextCompat 26 import androidx.core.content.ContextCompat
14 import androidx.lifecycle.LifecycleOwner 27 import androidx.lifecycle.LifecycleOwner
@@ -39,6 +52,7 @@ class MobileScanner( @@ -39,6 +52,7 @@ class MobileScanner(
39 private var scanner = BarcodeScanning.getClient() 52 private var scanner = BarcodeScanning.getClient()
40 private var lastScanned: List<String?>? = null 53 private var lastScanned: List<String?>? = null
41 private var scannerTimeout = false 54 private var scannerTimeout = false
  55 + private var displayListener: DisplayManager.DisplayListener? = null
42 56
43 /// Configurable variables 57 /// Configurable variables
44 var scanWindow: List<Float>? = null 58 var scanWindow: List<Float>? = null
@@ -64,7 +78,7 @@ class MobileScanner( @@ -64,7 +78,7 @@ class MobileScanner(
64 scanner.process(inputImage) 78 scanner.process(inputImage)
65 .addOnSuccessListener { barcodes -> 79 .addOnSuccessListener { barcodes ->
66 if (detectionSpeed == DetectionSpeed.NO_DUPLICATES) { 80 if (detectionSpeed == DetectionSpeed.NO_DUPLICATES) {
67 - val newScannedBarcodes = barcodes.map { barcode -> barcode.rawValue } 81 + val newScannedBarcodes = barcodes.mapNotNull({ barcode -> barcode.rawValue }).sorted()
68 if (newScannedBarcodes == lastScanned) { 82 if (newScannedBarcodes == lastScanned) {
69 // New scanned is duplicate, returning 83 // New scanned is duplicate, returning
70 return@addOnSuccessListener 84 return@addOnSuccessListener
@@ -102,14 +116,16 @@ class MobileScanner( @@ -102,14 +116,16 @@ class MobileScanner(
102 val stream = ByteArrayOutputStream() 116 val stream = ByteArrayOutputStream()
103 bmResult.compress(Bitmap.CompressFormat.PNG, 100, stream) 117 bmResult.compress(Bitmap.CompressFormat.PNG, 100, stream)
104 val byteArray = stream.toByteArray() 118 val byteArray = stream.toByteArray()
  119 + val bmWidth = bmResult.width
  120 + val bmHeight = bmResult.height
105 bmResult.recycle() 121 bmResult.recycle()
106 122
107 123
108 mobileScannerCallback( 124 mobileScannerCallback(
109 barcodeMap, 125 barcodeMap,
110 byteArray, 126 byteArray,
111 - bmResult.width,  
112 - bmResult.height 127 + bmWidth,
  128 + bmHeight
113 ) 129 )
114 130
115 } else { 131 } else {
@@ -138,7 +154,7 @@ class MobileScanner( @@ -138,7 +154,7 @@ class MobileScanner(
138 } 154 }
139 } 155 }
140 156
141 - fun rotateBitmap(bitmap: Bitmap, degrees: Float): Bitmap { 157 + private fun rotateBitmap(bitmap: Bitmap, degrees: Float): Bitmap {
142 val matrix = Matrix() 158 val matrix = Matrix()
143 matrix.postRotate(degrees) 159 matrix.postRotate(degrees)
144 return Bitmap.createBitmap(bitmap, 0, 0, bitmap.width, bitmap.height, matrix, true) 160 return Bitmap.createBitmap(bitmap, 0, 0, bitmap.width, bitmap.height, matrix, true)
@@ -166,6 +182,34 @@ class MobileScanner( @@ -166,6 +182,34 @@ class MobileScanner(
166 return scaledScanWindow.contains(barcodeBoundingBox) 182 return scaledScanWindow.contains(barcodeBoundingBox)
167 } 183 }
168 184
  185 + // Return the best resolution for the actual device orientation.
  186 + //
  187 + // By default the resolution is 480x640, which is too low for ML Kit.
  188 + // If the given resolution is not supported by the display,
  189 + // the closest available resolution is used.
  190 + //
  191 + // The resolution should be adjusted for the display rotation, to preserve the aspect ratio.
  192 + @Suppress("deprecation")
  193 + private fun getResolution(cameraResolution: Size): Size {
  194 + val rotation = if (Build.VERSION.SDK_INT >= 30) {
  195 + activity.display!!.rotation
  196 + } else {
  197 + val windowManager = activity.applicationContext.getSystemService(Context.WINDOW_SERVICE) as WindowManager
  198 +
  199 + windowManager.defaultDisplay.rotation
  200 + }
  201 +
  202 + val widthMaxRes = cameraResolution.width
  203 + val heightMaxRes = cameraResolution.height
  204 +
  205 + val targetResolution = if (rotation == Surface.ROTATION_0 || rotation == Surface.ROTATION_180) {
  206 + Size(widthMaxRes, heightMaxRes) // Portrait mode
  207 + } else {
  208 + Size(heightMaxRes, widthMaxRes) // Landscape mode
  209 + }
  210 + return targetResolution
  211 + }
  212 +
169 /** 213 /**
170 * Start barcode scanning by initializing the camera and barcode scanner. 214 * Start barcode scanning by initializing the camera and barcode scanner.
171 */ 215 */
@@ -179,16 +223,22 @@ class MobileScanner( @@ -179,16 +223,22 @@ class MobileScanner(
179 torchStateCallback: TorchStateCallback, 223 torchStateCallback: TorchStateCallback,
180 zoomScaleStateCallback: ZoomScaleStateCallback, 224 zoomScaleStateCallback: ZoomScaleStateCallback,
181 mobileScannerStartedCallback: MobileScannerStartedCallback, 225 mobileScannerStartedCallback: MobileScannerStartedCallback,
182 - detectionTimeout: Long 226 + mobileScannerErrorCallback: (exception: Exception) -> Unit,
  227 + detectionTimeout: Long,
  228 + cameraResolution: Size?,
  229 + newCameraResolutionSelector: Boolean
183 ) { 230 ) {
184 this.detectionSpeed = detectionSpeed 231 this.detectionSpeed = detectionSpeed
185 this.detectionTimeout = detectionTimeout 232 this.detectionTimeout = detectionTimeout
186 this.returnImage = returnImage 233 this.returnImage = returnImage
187 234
188 if (camera?.cameraInfo != null && preview != null && textureEntry != null) { 235 if (camera?.cameraInfo != null && preview != null && textureEntry != null) {
189 - throw AlreadyStarted() 236 + mobileScannerErrorCallback(AlreadyStarted())
  237 +
  238 + return
190 } 239 }
191 240
  241 + lastScanned = null
192 scanner = if (barcodeScannerOptions != null) { 242 scanner = if (barcodeScannerOptions != null) {
193 BarcodeScanning.getClient(barcodeScannerOptions) 243 BarcodeScanning.getClient(barcodeScannerOptions)
194 } else { 244 } else {
@@ -200,10 +250,15 @@ class MobileScanner( @@ -200,10 +250,15 @@ class MobileScanner(
200 250
201 cameraProviderFuture.addListener({ 251 cameraProviderFuture.addListener({
202 cameraProvider = cameraProviderFuture.get() 252 cameraProvider = cameraProviderFuture.get()
  253 + val nrOfCameras = cameraProvider?.availableCameraInfos?.size
  254 +
203 if (cameraProvider == null) { 255 if (cameraProvider == null) {
204 - throw CameraError() 256 + mobileScannerErrorCallback(CameraError())
  257 +
  258 + return@addListener
205 } 259 }
206 - cameraProvider!!.unbindAll() 260 +
  261 + cameraProvider?.unbindAll()
207 textureEntry = textureRegistry.createSurfaceTexture() 262 textureEntry = textureRegistry.createSurfaceTexture()
208 263
209 // Preview 264 // Preview
@@ -229,42 +284,97 @@ class MobileScanner( @@ -229,42 +284,97 @@ class MobileScanner(
229 // Build the analyzer to be passed on to MLKit 284 // Build the analyzer to be passed on to MLKit
230 val analysisBuilder = ImageAnalysis.Builder() 285 val analysisBuilder = ImageAnalysis.Builder()
231 .setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST) 286 .setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
232 -// analysisBuilder.setTargetResolution(Size(1440, 1920)) 287 + val displayManager = activity.applicationContext.getSystemService(Context.DISPLAY_SERVICE) as DisplayManager
  288 +
  289 + if (cameraResolution != null) {
  290 + if (newCameraResolutionSelector) {
  291 + val selectorBuilder = ResolutionSelector.Builder()
  292 + selectorBuilder.setResolutionStrategy(
  293 + ResolutionStrategy(
  294 + cameraResolution,
  295 + ResolutionStrategy.FALLBACK_RULE_CLOSEST_HIGHER_THEN_LOWER
  296 + )
  297 + )
  298 + analysisBuilder.setResolutionSelector(selectorBuilder.build()).build()
  299 + } else {
  300 + @Suppress("DEPRECATION")
  301 + analysisBuilder.setTargetResolution(getResolution(cameraResolution))
  302 + }
  303 +
  304 + if (displayListener == null) {
  305 + displayListener = object : DisplayManager.DisplayListener {
  306 + override fun onDisplayAdded(displayId: Int) {}
  307 +
  308 + override fun onDisplayRemoved(displayId: Int) {}
  309 +
  310 + override fun onDisplayChanged(displayId: Int) {
  311 + if (newCameraResolutionSelector) {
  312 + val selectorBuilder = ResolutionSelector.Builder()
  313 + selectorBuilder.setResolutionStrategy(
  314 + ResolutionStrategy(
  315 + cameraResolution,
  316 + ResolutionStrategy.FALLBACK_RULE_CLOSEST_HIGHER_THEN_LOWER
  317 + )
  318 + )
  319 + analysisBuilder.setResolutionSelector(selectorBuilder.build()).build()
  320 + } else {
  321 + @Suppress("DEPRECATION")
  322 + analysisBuilder.setTargetResolution(getResolution(cameraResolution))
  323 + }
  324 + }
  325 + }
  326 +
  327 + displayManager.registerDisplayListener(
  328 + displayListener, null,
  329 + )
  330 + }
  331 + }
  332 +
233 val analysis = analysisBuilder.build().apply { setAnalyzer(executor, captureOutput) } 333 val analysis = analysisBuilder.build().apply { setAnalyzer(executor, captureOutput) }
234 334
235 - camera = cameraProvider!!.bindToLifecycle( 335 + try {
  336 + camera = cameraProvider?.bindToLifecycle(
236 activity as LifecycleOwner, 337 activity as LifecycleOwner,
237 cameraPosition, 338 cameraPosition,
238 preview, 339 preview,
239 analysis 340 analysis
240 ) 341 )
  342 + } catch(exception: Exception) {
  343 + mobileScannerErrorCallback(NoCamera())
  344 +
  345 + return@addListener
  346 + }
241 347
  348 + camera?.let {
242 // Register the torch listener 349 // Register the torch listener
243 - camera!!.cameraInfo.torchState.observe(activity) { state -> 350 + it.cameraInfo.torchState.observe(activity as LifecycleOwner) { state ->
244 // TorchState.OFF = 0; TorchState.ON = 1 351 // TorchState.OFF = 0; TorchState.ON = 1
245 torchStateCallback(state) 352 torchStateCallback(state)
246 } 353 }
247 354
248 // Register the zoom scale listener 355 // Register the zoom scale listener
249 - camera!!.cameraInfo.zoomState.observe(activity) { state -> 356 + it.cameraInfo.zoomState.observe(activity) { state ->
250 zoomScaleStateCallback(state.linearZoom.toDouble()) 357 zoomScaleStateCallback(state.linearZoom.toDouble())
251 } 358 }
252 359
253 -  
254 // Enable torch if provided 360 // Enable torch if provided
255 - camera!!.cameraControl.enableTorch(torch) 361 + if (it.cameraInfo.hasFlashUnit()) {
  362 + it.cameraControl.enableTorch(torch)
  363 + }
  364 + }
256 365
257 val resolution = analysis.resolutionInfo!!.resolution 366 val resolution = analysis.resolutionInfo!!.resolution
258 - val portrait = camera!!.cameraInfo.sensorRotationDegrees % 180 == 0  
259 val width = resolution.width.toDouble() 367 val width = resolution.width.toDouble()
260 val height = resolution.height.toDouble() 368 val height = resolution.height.toDouble()
  369 + val portrait = (camera?.cameraInfo?.sensorRotationDegrees ?: 0) % 180 == 0
261 370
262 mobileScannerStartedCallback( 371 mobileScannerStartedCallback(
263 MobileScannerStartParameters( 372 MobileScannerStartParameters(
264 if (portrait) width else height, 373 if (portrait) width else height,
265 if (portrait) height else width, 374 if (portrait) height else width,
266 - camera!!.cameraInfo.hasFlashUnit(),  
267 - textureEntry!!.id() 375 + camera?.cameraInfo?.hasFlashUnit() ?: false,
  376 + textureEntry!!.id(),
  377 + nrOfCameras ?: 0
268 ) 378 )
269 ) 379 )
270 }, executor) 380 }, executor)
@@ -278,6 +388,13 @@ class MobileScanner( @@ -278,6 +388,13 @@ class MobileScanner(
278 throw AlreadyStopped() 388 throw AlreadyStopped()
279 } 389 }
280 390
  391 + if (displayListener != null) {
  392 + val displayManager = activity.applicationContext.getSystemService(Context.DISPLAY_SERVICE) as DisplayManager
  393 +
  394 + displayManager.unregisterDisplayListener(displayListener)
  395 + displayListener = null
  396 + }
  397 +
281 val owner = activity as LifecycleOwner 398 val owner = activity as LifecycleOwner
282 camera?.cameraInfo?.torchState?.removeObservers(owner) 399 camera?.cameraInfo?.torchState?.removeObservers(owner)
283 cameraProvider?.unbindAll() 400 cameraProvider?.unbindAll()
@@ -296,9 +413,12 @@ class MobileScanner( @@ -296,9 +413,12 @@ class MobileScanner(
296 */ 413 */
297 fun toggleTorch(enableTorch: Boolean) { 414 fun toggleTorch(enableTorch: Boolean) {
298 if (camera == null) { 415 if (camera == null) {
299 - throw TorchWhenStopped() 416 + return
  417 + }
  418 +
  419 + if (camera?.cameraInfo?.hasFlashUnit() == true) {
  420 + camera?.cameraControl?.enableTorch(enableTorch)
300 } 421 }
301 - camera!!.cameraControl.enableTorch(enableTorch)  
302 } 422 }
303 423
304 /** 424 /**
@@ -328,9 +448,9 @@ class MobileScanner( @@ -328,9 +448,9 @@ class MobileScanner(
328 * Set the zoom rate of the camera. 448 * Set the zoom rate of the camera.
329 */ 449 */
330 fun setScale(scale: Double) { 450 fun setScale(scale: Double) {
331 - if (camera == null) throw ZoomWhenStopped()  
332 if (scale > 1.0 || scale < 0) throw ZoomNotInRange() 451 if (scale > 1.0 || scale < 0) throw ZoomNotInRange()
333 - camera!!.cameraControl.setLinearZoom(scale.toFloat()) 452 + if (camera == null) throw ZoomWhenStopped()
  453 + camera?.cameraControl?.setLinearZoom(scale.toFloat())
334 } 454 }
335 455
336 /** 456 /**
@@ -338,7 +458,7 @@ class MobileScanner( @@ -338,7 +458,7 @@ class MobileScanner(
338 */ 458 */
339 fun resetScale() { 459 fun resetScale() {
340 if (camera == null) throw ZoomWhenStopped() 460 if (camera == null) throw ZoomWhenStopped()
341 - camera!!.cameraControl.setZoomRatio(1f) 461 + camera?.cameraControl?.setZoomRatio(1f)
342 } 462 }
343 463
344 } 464 }
@@ -3,8 +3,6 @@ package dev.steenbakker.mobile_scanner @@ -3,8 +3,6 @@ package dev.steenbakker.mobile_scanner
3 class NoCamera : Exception() 3 class NoCamera : Exception()
4 class AlreadyStarted : Exception() 4 class AlreadyStarted : Exception()
5 class AlreadyStopped : Exception() 5 class AlreadyStopped : Exception()
6 -class TorchError : Exception()  
7 class CameraError : Exception() 6 class CameraError : Exception()
8 -class TorchWhenStopped : Exception()  
9 class ZoomWhenStopped : Exception() 7 class ZoomWhenStopped : Exception()
10 class ZoomNotInRange : Exception() 8 class ZoomNotInRange : Exception()
@@ -2,6 +2,9 @@ package dev.steenbakker.mobile_scanner @@ -2,6 +2,9 @@ package dev.steenbakker.mobile_scanner
2 2
3 import android.app.Activity 3 import android.app.Activity
4 import android.net.Uri 4 import android.net.Uri
  5 +import android.os.Handler
  6 +import android.os.Looper
  7 +import android.util.Size
5 import androidx.camera.core.CameraSelector 8 import androidx.camera.core.CameraSelector
6 import androidx.camera.core.ExperimentalGetImage 9 import androidx.camera.core.ExperimentalGetImage
7 import com.google.mlkit.vision.barcode.BarcodeScannerOptions 10 import com.google.mlkit.vision.barcode.BarcodeScannerOptions
@@ -29,12 +32,13 @@ class MobileScannerHandler( @@ -29,12 +32,13 @@ class MobileScannerHandler(
29 "name" to "barcode", 32 "name" to "barcode",
30 "data" to barcodes 33 "data" to barcodes
31 )) 34 ))
32 - analyzerResult?.success(true)  
33 - } else {  
34 - analyzerResult?.success(false)  
35 } 35 }
  36 +
  37 + Handler(Looper.getMainLooper()).post {
  38 + analyzerResult?.success(barcodes != null)
36 analyzerResult = null 39 analyzerResult = null
37 } 40 }
  41 + }
38 42
39 private var analyzerResult: MethodChannel.Result? = null 43 private var analyzerResult: MethodChannel.Result? = null
40 44
@@ -91,7 +95,6 @@ class MobileScannerHandler( @@ -91,7 +95,6 @@ class MobileScannerHandler(
91 if(listener != null) { 95 if(listener != null) {
92 activityPluginBinding.removeRequestPermissionsResultListener(listener) 96 activityPluginBinding.removeRequestPermissionsResultListener(listener)
93 } 97 }
94 -  
95 } 98 }
96 99
97 @ExperimentalGetImage 100 @ExperimentalGetImage
@@ -119,8 +122,8 @@ class MobileScannerHandler( @@ -119,8 +122,8 @@ class MobileScannerHandler(
119 "stop" -> stop(result) 122 "stop" -> stop(result)
120 "analyzeImage" -> analyzeImage(call, result) 123 "analyzeImage" -> analyzeImage(call, result)
121 "setScale" -> setScale(call, result) 124 "setScale" -> setScale(call, result)
122 - "resetScale" -> resetScale(call, result)  
123 - "updateScanWindow" -> updateScanWindow(call) 125 + "resetScale" -> resetScale(result)
  126 + "updateScanWindow" -> updateScanWindow(call, result)
124 else -> result.notImplemented() 127 else -> result.notImplemented()
125 } 128 }
126 } 129 }
@@ -133,12 +136,19 @@ class MobileScannerHandler( @@ -133,12 +136,19 @@ class MobileScannerHandler(
133 val returnImage: Boolean = call.argument<Boolean>("returnImage") ?: false 136 val returnImage: Boolean = call.argument<Boolean>("returnImage") ?: false
134 val speed: Int = call.argument<Int>("speed") ?: 1 137 val speed: Int = call.argument<Int>("speed") ?: 1
135 val timeout: Int = call.argument<Int>("timeout") ?: 250 138 val timeout: Int = call.argument<Int>("timeout") ?: 250
  139 + val cameraResolutionValues: List<Int>? = call.argument<List<Int>>("cameraResolution")
  140 + val useNewCameraSelector: Boolean = call.argument<Boolean>("useNewCameraSelector") ?: false
  141 + val cameraResolution: Size? = if (cameraResolutionValues != null) {
  142 + Size(cameraResolutionValues[0], cameraResolutionValues[1])
  143 + } else {
  144 + null
  145 + }
136 146
137 var barcodeScannerOptions: BarcodeScannerOptions? = null 147 var barcodeScannerOptions: BarcodeScannerOptions? = null
138 if (formats != null) { 148 if (formats != null) {
139 val formatsList: MutableList<Int> = mutableListOf() 149 val formatsList: MutableList<Int> = mutableListOf()
140 - for (index in formats) {  
141 - formatsList.add(BarcodeFormats.values()[index].intValue) 150 + for (formatValue in formats) {
  151 + formatsList.add(BarcodeFormats.fromRawValue(formatValue).intValue)
142 } 152 }
143 barcodeScannerOptions = if (formatsList.size == 1) { 153 barcodeScannerOptions = if (formatsList.size == 1) {
144 BarcodeScannerOptions.Builder().setBarcodeFormats(formatsList.first()) 154 BarcodeScannerOptions.Builder().setBarcodeFormats(formatsList.first())
@@ -156,48 +166,63 @@ class MobileScannerHandler( @@ -156,48 +166,63 @@ class MobileScannerHandler(
156 166
157 val detectionSpeed: DetectionSpeed = DetectionSpeed.values().first { it.intValue == speed} 167 val detectionSpeed: DetectionSpeed = DetectionSpeed.values().first { it.intValue == speed}
158 168
159 - try {  
160 - mobileScanner!!.start(barcodeScannerOptions, returnImage, position, torch, detectionSpeed, torchStateCallback, zoomScaleStateCallback, mobileScannerStartedCallback = { 169 + mobileScanner!!.start(
  170 + barcodeScannerOptions,
  171 + returnImage,
  172 + position,
  173 + torch,
  174 + detectionSpeed,
  175 + torchStateCallback,
  176 + zoomScaleStateCallback,
  177 + mobileScannerStartedCallback = {
  178 + Handler(Looper.getMainLooper()).post {
161 result.success(mapOf( 179 result.success(mapOf(
162 "textureId" to it.id, 180 "textureId" to it.id,
163 "size" to mapOf("width" to it.width, "height" to it.height), 181 "size" to mapOf("width" to it.width, "height" to it.height),
164 - "torchable" to it.hasFlashUnit 182 + "torchable" to it.hasFlashUnit,
  183 + "nrOfCameras" to it.nrOfCameras
165 )) 184 ))
  185 + }
166 }, 186 },
167 - timeout.toLong())  
168 -  
169 - } catch (e: AlreadyStarted) { 187 + mobileScannerErrorCallback = {
  188 + Handler(Looper.getMainLooper()).post {
  189 + when (it) {
  190 + is AlreadyStarted -> {
170 result.error( 191 result.error(
171 "MobileScanner", 192 "MobileScanner",
172 "Called start() while already started", 193 "Called start() while already started",
173 null 194 null
174 ) 195 )
175 - } catch (e: NoCamera) {  
176 - result.error(  
177 - "MobileScanner",  
178 - "No camera found or failed to open camera!",  
179 - null  
180 - )  
181 - } catch (e: TorchError) { 196 + }
  197 + is CameraError -> {
182 result.error( 198 result.error(
183 "MobileScanner", 199 "MobileScanner",
184 - "Error occurred when setting torch!", 200 + "Error occurred when setting up camera!",
185 null 201 null
186 ) 202 )
187 - } catch (e: CameraError) { 203 + }
  204 + is NoCamera -> {
188 result.error( 205 result.error(
189 "MobileScanner", 206 "MobileScanner",
190 - "Error occurred when setting up camera!", 207 + "No camera found or failed to open camera!",
191 null 208 null
192 ) 209 )
193 - } catch (e: Exception) { 210 + }
  211 + else -> {
194 result.error( 212 result.error(
195 "MobileScanner", 213 "MobileScanner",
196 - "Unknown error occurred..", 214 + "Unknown error occurred.",
197 null 215 null
198 ) 216 )
199 } 217 }
200 } 218 }
  219 + }
  220 + },
  221 + timeout.toLong(),
  222 + cameraResolution,
  223 + useNewCameraSelector
  224 + )
  225 + }
201 226
202 private fun stop(result: MethodChannel.Result) { 227 private fun stop(result: MethodChannel.Result) {
203 try { 228 try {
@@ -215,12 +240,8 @@ class MobileScannerHandler( @@ -215,12 +240,8 @@ class MobileScannerHandler(
215 } 240 }
216 241
217 private fun toggleTorch(call: MethodCall, result: MethodChannel.Result) { 242 private fun toggleTorch(call: MethodCall, result: MethodChannel.Result) {
218 - try {  
219 mobileScanner!!.toggleTorch(call.arguments == 1) 243 mobileScanner!!.toggleTorch(call.arguments == 1)
220 result.success(null) 244 result.success(null)
221 - } catch (e: AlreadyStopped) {  
222 - result.error("MobileScanner", "Called toggleTorch() while stopped!", null)  
223 - }  
224 } 245 }
225 246
226 private fun setScale(call: MethodCall, result: MethodChannel.Result) { 247 private fun setScale(call: MethodCall, result: MethodChannel.Result) {
@@ -234,7 +255,7 @@ class MobileScannerHandler( @@ -234,7 +255,7 @@ class MobileScannerHandler(
234 } 255 }
235 } 256 }
236 257
237 - private fun resetScale(call: MethodCall, result: MethodChannel.Result) { 258 + private fun resetScale(result: MethodChannel.Result) {
238 try { 259 try {
239 mobileScanner!!.resetScale() 260 mobileScanner!!.resetScale()
240 result.success(null) 261 result.success(null)
@@ -243,7 +264,9 @@ class MobileScannerHandler( @@ -243,7 +264,9 @@ class MobileScannerHandler(
243 } 264 }
244 } 265 }
245 266
246 - private fun updateScanWindow(call: MethodCall) { 267 + private fun updateScanWindow(call: MethodCall, result: MethodChannel.Result) {
247 mobileScanner!!.scanWindow = call.argument<List<Float>?>("rect") 268 mobileScanner!!.scanWindow = call.argument<List<Float>?>("rect")
  269 +
  270 + result.success(null)
248 } 271 }
249 } 272 }
@@ -45,7 +45,7 @@ class MobileScannerPermissions { @@ -45,7 +45,7 @@ class MobileScannerPermissions {
45 return if (hasPermission) { 45 return if (hasPermission) {
46 1 46 1
47 } else { 47 } else {
48 - 0 48 + 2
49 } 49 }
50 } 50 }
51 51
@@ -70,6 +70,7 @@ class MobileScannerPermissions { @@ -70,6 +70,7 @@ class MobileScannerPermissions {
70 object: ResultCallback { 70 object: ResultCallback {
71 override fun onResult(errorCode: String?, errorDescription: String?) { 71 override fun onResult(errorCode: String?, errorDescription: String?) {
72 ongoing = false 72 ongoing = false
  73 + listener = null
73 callback.onResult(errorCode, errorDescription) 74 callback.onResult(errorCode, errorDescription)
74 } 75 }
75 } 76 }
@@ -2,17 +2,41 @@ package dev.steenbakker.mobile_scanner.objects @@ -2,17 +2,41 @@ package dev.steenbakker.mobile_scanner.objects
2 2
3 enum class BarcodeFormats(val intValue: Int) { 3 enum class BarcodeFormats(val intValue: Int) {
4 UNKNOWN(com.google.mlkit.vision.barcode.common.Barcode.FORMAT_UNKNOWN), 4 UNKNOWN(com.google.mlkit.vision.barcode.common.Barcode.FORMAT_UNKNOWN),
5 - ALL_FORMATS(com.google.mlkit.vision.barcode.common.Barcode.FORMAT_ALL_FORMATS), CODE_128(com.google.mlkit.vision.barcode.common.Barcode.FORMAT_CODE_128), CODE_39(  
6 - com.google.mlkit.vision.barcode.common.Barcode.FORMAT_CODE_39  
7 - ),  
8 - CODE_93(com.google.mlkit.vision.barcode.common.Barcode.FORMAT_CODE_93), CODABAR(com.google.mlkit.vision.barcode.common.Barcode.FORMAT_CODABAR), DATA_MATRIX(  
9 - com.google.mlkit.vision.barcode.common.Barcode.FORMAT_DATA_MATRIX  
10 - ),  
11 - EAN_13(com.google.mlkit.vision.barcode.common.Barcode.FORMAT_EAN_13), EAN_8(com.google.mlkit.vision.barcode.common.Barcode.FORMAT_EAN_8), ITF(  
12 - com.google.mlkit.vision.barcode.common.Barcode.FORMAT_ITF  
13 - ),  
14 - QR_CODE(com.google.mlkit.vision.barcode.common.Barcode.FORMAT_QR_CODE), UPC_A(com.google.mlkit.vision.barcode.common.Barcode.FORMAT_UPC_A), UPC_E(  
15 - com.google.mlkit.vision.barcode.common.Barcode.FORMAT_UPC_E  
16 - ),  
17 - PDF417(com.google.mlkit.vision.barcode.common.Barcode.FORMAT_PDF417), AZTEC(com.google.mlkit.vision.barcode.common.Barcode.FORMAT_AZTEC); 5 + ALL_FORMATS(com.google.mlkit.vision.barcode.common.Barcode.FORMAT_ALL_FORMATS),
  6 + CODE_128(com.google.mlkit.vision.barcode.common.Barcode.FORMAT_CODE_128),
  7 + CODE_39(com.google.mlkit.vision.barcode.common.Barcode.FORMAT_CODE_39),
  8 + CODE_93(com.google.mlkit.vision.barcode.common.Barcode.FORMAT_CODE_93),
  9 + CODABAR(com.google.mlkit.vision.barcode.common.Barcode.FORMAT_CODABAR),
  10 + DATA_MATRIX(com.google.mlkit.vision.barcode.common.Barcode.FORMAT_DATA_MATRIX),
  11 + EAN_13(com.google.mlkit.vision.barcode.common.Barcode.FORMAT_EAN_13),
  12 + EAN_8(com.google.mlkit.vision.barcode.common.Barcode.FORMAT_EAN_8),
  13 + ITF(com.google.mlkit.vision.barcode.common.Barcode.FORMAT_ITF),
  14 + QR_CODE(com.google.mlkit.vision.barcode.common.Barcode.FORMAT_QR_CODE),
  15 + UPC_A(com.google.mlkit.vision.barcode.common.Barcode.FORMAT_UPC_A),
  16 + UPC_E(com.google.mlkit.vision.barcode.common.Barcode.FORMAT_UPC_E),
  17 + PDF417(com.google.mlkit.vision.barcode.common.Barcode.FORMAT_PDF417),
  18 + AZTEC(com.google.mlkit.vision.barcode.common.Barcode.FORMAT_AZTEC);
  19 +
  20 + companion object {
  21 + fun fromRawValue(rawValue: Int): BarcodeFormats {
  22 + return when(rawValue) {
  23 + -1 -> UNKNOWN
  24 + 0 -> ALL_FORMATS
  25 + 1 -> CODE_128
  26 + 2 -> CODE_39
  27 + 4 -> CODE_93
  28 + 8 -> CODABAR
  29 + 16 -> DATA_MATRIX
  30 + 32 -> EAN_13
  31 + 64 -> EAN_8
  32 + 128 -> ITF
  33 + 256 -> QR_CODE
  34 + 512 -> UPC_A
  35 + 1024 -> UPC_E
  36 + 2048 -> PDF417
  37 + 4096 -> AZTEC
  38 + else -> UNKNOWN
  39 + }
  40 + }
  41 + }
18 } 42 }
@@ -4,5 +4,6 @@ class MobileScannerStartParameters( @@ -4,5 +4,6 @@ class MobileScannerStartParameters(
4 val width: Double = 0.0, 4 val width: Double = 0.0,
5 val height: Double, 5 val height: Double,
6 val hasFlashUnit: Boolean, 6 val hasFlashUnit: Boolean,
7 - val id: Long 7 + val id: Long,
  8 + val nrOfCameras: Int
8 ) 9 )
@@ -8,6 +8,7 @@ @@ -8,6 +8,7 @@
8 .buildlog/ 8 .buildlog/
9 .history 9 .history
10 .svn/ 10 .svn/
  11 +migrate_working_dir/
11 12
12 # IntelliJ related 13 # IntelliJ related
13 *.iml 14 *.iml
@@ -31,8 +32,6 @@ @@ -31,8 +32,6 @@
31 .pub/ 32 .pub/
32 /build/ 33 /build/
33 34
34 -# Web related  
35 -  
36 # Symbolication related 35 # Symbolication related
37 app.*.symbols 36 app.*.symbols
38 37
1 -# This file tracks properties of this Flutter project.  
2 -# Used by Flutter tool to assess capabilities and perform upgrades etc.  
3 -#  
4 -# This file should be version controlled and should not be manually edited.  
5 -  
6 -version:  
7 - revision: 5f105a6ca7a5ac7b8bc9b241f4c2d86f4188cf5c  
8 - channel: stable  
9 -  
10 -project_type: app  
@@ -8,9 +8,9 @@ This project is a starting point for a Flutter application. @@ -8,9 +8,9 @@ This project is a starting point for a Flutter application.
8 8
9 A few resources to get you started if this is your first Flutter project: 9 A few resources to get you started if this is your first Flutter project:
10 10
11 -- [Lab: Write your first Flutter app](https://flutter.dev/docs/get-started/codelab)  
12 -- [Cookbook: Useful Flutter samples](https://flutter.dev/docs/cookbook) 11 +- [Lab: Write your first Flutter app](https://docs.flutter.dev/get-started/codelab)
  12 +- [Cookbook: Useful Flutter samples](https://docs.flutter.dev/cookbook)
13 13
14 -For help getting started with Flutter, view our  
15 -[online documentation](https://flutter.dev/docs), which offers tutorials, 14 +For help getting started with Flutter development, view the
  15 +[online documentation](https://docs.flutter.dev/), which offers tutorials,
16 samples, guidance on mobile development, and a full API reference. 16 samples, guidance on mobile development, and a full API reference.
  1 +plugins {
  2 + id "com.android.application"
  3 + id "kotlin-android"
  4 + id "dev.flutter.flutter-gradle-plugin"
  5 +}
  6 +
1 def localProperties = new Properties() 7 def localProperties = new Properties()
2 def localPropertiesFile = rootProject.file('local.properties') 8 def localPropertiesFile = rootProject.file('local.properties')
3 if (localPropertiesFile.exists()) { 9 if (localPropertiesFile.exists()) {
@@ -6,11 +12,6 @@ if (localPropertiesFile.exists()) { @@ -6,11 +12,6 @@ if (localPropertiesFile.exists()) {
6 } 12 }
7 } 13 }
8 14
9 -def flutterRoot = localProperties.getProperty('flutter.sdk')  
10 -if (flutterRoot == null) {  
11 - throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.")  
12 -}  
13 -  
14 def flutterVersionCode = localProperties.getProperty('flutter.versionCode') 15 def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
15 if (flutterVersionCode == null) { 16 if (flutterVersionCode == null) {
16 flutterVersionCode = '1' 17 flutterVersionCode = '1'
@@ -21,20 +22,19 @@ if (flutterVersionName == null) { @@ -21,20 +22,19 @@ if (flutterVersionName == null) {
21 flutterVersionName = '1.0' 22 flutterVersionName = '1.0'
22 } 23 }
23 24
24 -apply plugin: 'com.android.application'  
25 -apply plugin: 'kotlin-android'  
26 -apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"  
27 -  
28 android { 25 android {
29 - compileSdkVersion 33 26 + namespace "dev.steenbakker.mobile_scanner_example"
  27 + compileSdk 34
  28 + ndkVersion "25.1.8937393"
  29 +// ndkVersion flutter.ndkVersion
30 30
31 compileOptions { 31 compileOptions {
32 - sourceCompatibility JavaVersion.VERSION_1_8  
33 - targetCompatibility JavaVersion.VERSION_1_8 32 + sourceCompatibility JavaVersion.VERSION_17
  33 + targetCompatibility JavaVersion.VERSION_17
34 } 34 }
35 35
36 kotlinOptions { 36 kotlinOptions {
37 - jvmTarget = '1.8' 37 + jvmTarget = '17'
38 } 38 }
39 39
40 sourceSets { 40 sourceSets {
@@ -45,7 +45,7 @@ android { @@ -45,7 +45,7 @@ android {
45 // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). 45 // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
46 applicationId "dev.steenbakker.mobile_scanner_example" 46 applicationId "dev.steenbakker.mobile_scanner_example"
47 minSdkVersion 21 47 minSdkVersion 21
48 - targetSdkVersion 33 48 + targetSdkVersion 34
49 versionCode flutterVersionCode.toInteger() 49 versionCode flutterVersionCode.toInteger()
50 versionName flutterVersionName 50 versionName flutterVersionName
51 } 51 }
@@ -57,7 +57,6 @@ android { @@ -57,7 +57,6 @@ android {
57 signingConfig signingConfigs.debug 57 signingConfig signingConfigs.debug
58 } 58 }
59 } 59 }
60 - namespace 'dev.steenbakker.mobile_scanner_example'  
61 } 60 }
62 61
63 flutter { 62 flutter {
1 <manifest xmlns:android="http://schemas.android.com/apk/res/android"> 1 <manifest xmlns:android="http://schemas.android.com/apk/res/android">
2 - <!-- Flutter needs it to communicate with the running application 2 + <!-- The INTERNET permission is required for development. Specifically,
  3 + the Flutter tool needs it to communicate with the running application
3 to allow setting breakpoints, to provide hot reload, etc. 4 to allow setting breakpoints, to provide hot reload, etc.
4 --> 5 -->
5 <uses-permission android:name="android.permission.INTERNET"/> 6 <uses-permission android:name="android.permission.INTERNET"/>
1 -<manifest xmlns:android="http://schemas.android.com/apk/res/android"  
2 - package="dev.steenbakker.mobile_scanner_example">  
3 - 1 +<manifest xmlns:android="http://schemas.android.com/apk/res/android">
4 <application 2 <application
5 android:label="mobile_scanner_example" 3 android:label="mobile_scanner_example"
  4 + android:name="${applicationName}"
6 android:icon="@mipmap/ic_launcher"> 5 android:icon="@mipmap/ic_launcher">
7 <activity 6 <activity
8 android:name=".MainActivity" 7 android:name=".MainActivity"
1 package dev.steenbakker.mobile_scanner_example 1 package dev.steenbakker.mobile_scanner_example
2 2
3 -import io.flutter.embedding.android.FlutterFragmentActivity 3 +import io.flutter.embedding.android.FlutterActivity
4 4
5 -class MainActivity : FlutterFragmentActivity() { 5 +class MainActivity: FlutterActivity() {
6 } 6 }
@@ -3,7 +3,7 @@ @@ -3,7 +3,7 @@
3 <!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is on --> 3 <!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is on -->
4 <style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar"> 4 <style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
5 <!-- Show a splash screen on the activity. Automatically removed when 5 <!-- Show a splash screen on the activity. Automatically removed when
6 - Flutter draws its first frame --> 6 + the Flutter engine draws its first frame -->
7 <item name="android:windowBackground">@drawable/launch_background</item> 7 <item name="android:windowBackground">@drawable/launch_background</item>
8 </style> 8 </style>
9 <!-- Theme applied to the Android Window as soon as the process has started. 9 <!-- Theme applied to the Android Window as soon as the process has started.
@@ -3,7 +3,7 @@ @@ -3,7 +3,7 @@
3 <!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is off --> 3 <!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is off -->
4 <style name="LaunchTheme" parent="@android:style/Theme.Light.NoTitleBar"> 4 <style name="LaunchTheme" parent="@android:style/Theme.Light.NoTitleBar">
5 <!-- Show a splash screen on the activity. Automatically removed when 5 <!-- Show a splash screen on the activity. Automatically removed when
6 - Flutter draws its first frame --> 6 + the Flutter engine draws its first frame -->
7 <item name="android:windowBackground">@drawable/launch_background</item> 7 <item name="android:windowBackground">@drawable/launch_background</item>
8 </style> 8 </style>
9 <!-- Theme applied to the Android Window as soon as the process has started. 9 <!-- Theme applied to the Android Window as soon as the process has started.
1 <manifest xmlns:android="http://schemas.android.com/apk/res/android"> 1 <manifest xmlns:android="http://schemas.android.com/apk/res/android">
2 - <!-- Flutter needs it to communicate with the running application 2 + <!-- The INTERNET permission is required for development. Specifically,
  3 + the Flutter tool needs it to communicate with the running application
3 to allow setting breakpoints, to provide hot reload, etc. 4 to allow setting breakpoints, to provide hot reload, etc.
4 --> 5 -->
5 <uses-permission android:name="android.permission.INTERNET"/> 6 <uses-permission android:name="android.permission.INTERNET"/>
@@ -6,7 +6,7 @@ buildscript { @@ -6,7 +6,7 @@ buildscript {
6 } 6 }
7 7
8 dependencies { 8 dependencies {
9 - classpath 'com.android.tools.build:gradle:8.1.1' 9 + classpath 'com.android.tools.build:gradle:8.2.0'
10 classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 10 classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
11 } 11 }
12 } 12 }
1 -#Tue Jun 27 18:47:05 CEST 2023  
2 distributionBase=GRADLE_USER_HOME 1 distributionBase=GRADLE_USER_HOME
3 distributionPath=wrapper/dists 2 distributionPath=wrapper/dists
4 -distributionUrl=https\://services.gradle.org/distributions/gradle-8.1.1-bin.zip 3 +distributionUrl=https\://services.gradle.org/distributions/gradle-8.2-bin.zip
5 zipStoreBase=GRADLE_USER_HOME 4 zipStoreBase=GRADLE_USER_HOME
6 zipStorePath=wrapper/dists 5 zipStorePath=wrapper/dists
1 -include ':app' 1 +pluginManagement {
  2 + def flutterSdkPath = {
  3 + def properties = new Properties()
  4 + file("local.properties").withInputStream { properties.load(it) }
  5 + def flutterSdkPath = properties.getProperty("flutter.sdk")
  6 + assert flutterSdkPath != null, "flutter.sdk not set in local.properties"
  7 + return flutterSdkPath
  8 + }
  9 + settings.ext.flutterSdkPath = flutterSdkPath()
2 10
3 -def localPropertiesFile = new File(rootProject.projectDir, "local.properties")  
4 -def properties = new Properties() 11 + includeBuild("${settings.ext.flutterSdkPath}/packages/flutter_tools/gradle")
  12 +}
5 13
6 -assert localPropertiesFile.exists()  
7 -localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) } 14 +include ":app"
8 15
9 -def flutterSdkPath = properties.getProperty("flutter.sdk")  
10 -assert flutterSdkPath != null, "flutter.sdk not set in local.properties"  
11 -apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle" 16 +apply from: "${settings.ext.flutterSdkPath}/packages/flutter_tools/gradle/app_plugin_loader.gradle"
1 # Uncomment this line to define a global platform for your project 1 # Uncomment this line to define a global platform for your project
2 -platform :ios, '11.0' 2 +# platform :ios, '11.0'
3 3
4 # CocoaPods analytics sends network stats synchronously affecting flutter build latency. 4 # CocoaPods analytics sends network stats synchronously affecting flutter build latency.
5 ENV['COCOAPODS_DISABLE_STATS'] = 'true' 5 ENV['COCOAPODS_DISABLE_STATS'] = 'true'
@@ -32,13 +32,15 @@ target 'Runner' do @@ -32,13 +32,15 @@ target 'Runner' do
32 use_modular_headers! 32 use_modular_headers!
33 33
34 flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) 34 flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))
  35 + target 'RunnerTests' do
  36 + inherit! :search_paths
  37 + end
35 end 38 end
36 39
37 post_install do |installer| 40 post_install do |installer|
38 installer.pods_project.targets.each do |target| 41 installer.pods_project.targets.each do |target|
39 flutter_additional_ios_build_settings(target) 42 flutter_additional_ios_build_settings(target)
40 target.build_configurations.each do |config| 43 target.build_configurations.each do |config|
41 - config.build_settings['ENABLE_BITCODE'] = 'NO'  
42 config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '11.0' 44 config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '11.0'
43 end 45 end
44 end 46 end
@@ -8,14 +8,26 @@ @@ -8,14 +8,26 @@
8 8
9 /* Begin PBXBuildFile section */ 9 /* Begin PBXBuildFile section */
10 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; 10 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
  11 + 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; };
11 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; 12 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
12 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; 13 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; };
13 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 14 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
14 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 15 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
15 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; 16 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
16 - A5A2C2B73A9F26060DE9FB22 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 54E006799E73DEAB41FD3623 /* Pods_Runner.framework */; }; 17 + A277671F4E603C31E191FCBF /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FB50C06655B1959BAA8EAF64 /* Pods_Runner.framework */; };
  18 + F8C489EFA0FC3A131EBA1838 /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9F20A7A668B7797BBB5443C1 /* Pods_RunnerTests.framework */; };
17 /* End PBXBuildFile section */ 19 /* End PBXBuildFile section */
18 20
  21 +/* Begin PBXContainerItemProxy section */
  22 + 331C8085294A63A400263BE5 /* PBXContainerItemProxy */ = {
  23 + isa = PBXContainerItemProxy;
  24 + containerPortal = 97C146E61CF9000F007C117D /* Project object */;
  25 + proxyType = 1;
  26 + remoteGlobalIDString = 97C146ED1CF9000F007C117D;
  27 + remoteInfo = Runner;
  28 + };
  29 +/* End PBXContainerItemProxy section */
  30 +
19 /* Begin PBXCopyFilesBuildPhase section */ 31 /* Begin PBXCopyFilesBuildPhase section */
20 9705A1C41CF9048500538489 /* Embed Frameworks */ = { 32 9705A1C41CF9048500538489 /* Embed Frameworks */ = {
21 isa = PBXCopyFilesBuildPhase; 33 isa = PBXCopyFilesBuildPhase;
@@ -32,12 +44,14 @@ @@ -32,12 +44,14 @@
32 /* Begin PBXFileReference section */ 44 /* Begin PBXFileReference section */
33 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = "<group>"; }; 45 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = "<group>"; };
34 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = "<group>"; }; 46 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = "<group>"; };
35 - 32FD382A786B3A0080FE63FD /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = "<group>"; }; 47 + 331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = "<group>"; };
  48 + 331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
  49 + 337C24E5CFDB233CDBA51D9D /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = "<group>"; };
36 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = "<group>"; }; 50 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = "<group>"; };
37 - 54E006799E73DEAB41FD3623 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; };  
38 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = "<group>"; }; 51 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = "<group>"; };
39 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; }; 52 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
40 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = "<group>"; }; 53 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = "<group>"; };
  54 + 7F6B8553738879C25774B609 /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = "<group>"; };
41 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = "<group>"; }; 55 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = "<group>"; };
42 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = "<group>"; }; 56 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = "<group>"; };
43 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; 57 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; };
@@ -45,30 +59,40 @@ @@ -45,30 +59,40 @@
45 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; }; 59 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
46 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; }; 60 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
47 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; }; 61 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
48 - D5B36FCD262B39F867CFDEEE /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = "<group>"; };  
49 - F0D5742F0690BE32D07B033A /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = "<group>"; }; 62 + 9F20A7A668B7797BBB5443C1 /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
  63 + A5A24D61913C510FB38A434E /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = "<group>"; };
  64 + ADC5B74E7E0BCF65C9CD3AA9 /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = "<group>"; };
  65 + CB190BADFE09EBDD6E5B44BE /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = "<group>"; };
  66 + F3732C1D66ACA2383B88CDC2 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = "<group>"; };
  67 + FB50C06655B1959BAA8EAF64 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; };
50 /* End PBXFileReference section */ 68 /* End PBXFileReference section */
51 69
52 /* Begin PBXFrameworksBuildPhase section */ 70 /* Begin PBXFrameworksBuildPhase section */
  71 + 0F3F7D03C80D20F6F18A5A17 /* Frameworks */ = {
  72 + isa = PBXFrameworksBuildPhase;
  73 + buildActionMask = 2147483647;
  74 + files = (
  75 + F8C489EFA0FC3A131EBA1838 /* Pods_RunnerTests.framework in Frameworks */,
  76 + );
  77 + runOnlyForDeploymentPostprocessing = 0;
  78 + };
53 97C146EB1CF9000F007C117D /* Frameworks */ = { 79 97C146EB1CF9000F007C117D /* Frameworks */ = {
54 isa = PBXFrameworksBuildPhase; 80 isa = PBXFrameworksBuildPhase;
55 buildActionMask = 2147483647; 81 buildActionMask = 2147483647;
56 files = ( 82 files = (
57 - A5A2C2B73A9F26060DE9FB22 /* Pods_Runner.framework in Frameworks */, 83 + A277671F4E603C31E191FCBF /* Pods_Runner.framework in Frameworks */,
58 ); 84 );
59 runOnlyForDeploymentPostprocessing = 0; 85 runOnlyForDeploymentPostprocessing = 0;
60 }; 86 };
61 /* End PBXFrameworksBuildPhase section */ 87 /* End PBXFrameworksBuildPhase section */
62 88
63 /* Begin PBXGroup section */ 89 /* Begin PBXGroup section */
64 - 203D5C95A734778D93D18369 /* Pods */ = { 90 + 331C8082294A63A400263BE5 /* RunnerTests */ = {
65 isa = PBXGroup; 91 isa = PBXGroup;
66 children = ( 92 children = (
67 - D5B36FCD262B39F867CFDEEE /* Pods-Runner.debug.xcconfig */,  
68 - 32FD382A786B3A0080FE63FD /* Pods-Runner.release.xcconfig */,  
69 - F0D5742F0690BE32D07B033A /* Pods-Runner.profile.xcconfig */, 93 + 331C807B294A618700263BE5 /* RunnerTests.swift */,
70 ); 94 );
71 - path = Pods; 95 + path = RunnerTests;
72 sourceTree = "<group>"; 96 sourceTree = "<group>";
73 }; 97 };
74 9740EEB11CF90186004384FC /* Flutter */ = { 98 9740EEB11CF90186004384FC /* Flutter */ = {
@@ -88,8 +112,9 @@ @@ -88,8 +112,9 @@
88 9740EEB11CF90186004384FC /* Flutter */, 112 9740EEB11CF90186004384FC /* Flutter */,
89 97C146F01CF9000F007C117D /* Runner */, 113 97C146F01CF9000F007C117D /* Runner */,
90 97C146EF1CF9000F007C117D /* Products */, 114 97C146EF1CF9000F007C117D /* Products */,
91 - 203D5C95A734778D93D18369 /* Pods */,  
92 - FF36E403CAC9E06A5A96BB9F /* Frameworks */, 115 + 331C8082294A63A400263BE5 /* RunnerTests */,
  116 + C3326B04728E979C0CAEA680 /* Pods */,
  117 + E31F04DB28A3D0F29210F29D /* Frameworks */,
93 ); 118 );
94 sourceTree = "<group>"; 119 sourceTree = "<group>";
95 }; 120 };
@@ -97,6 +122,7 @@ @@ -97,6 +122,7 @@
97 isa = PBXGroup; 122 isa = PBXGroup;
98 children = ( 123 children = (
99 97C146EE1CF9000F007C117D /* Runner.app */, 124 97C146EE1CF9000F007C117D /* Runner.app */,
  125 + 331C8081294A63A400263BE5 /* RunnerTests.xctest */,
100 ); 126 );
101 name = Products; 127 name = Products;
102 sourceTree = "<group>"; 128 sourceTree = "<group>";
@@ -116,10 +142,24 @@ @@ -116,10 +142,24 @@
116 path = Runner; 142 path = Runner;
117 sourceTree = "<group>"; 143 sourceTree = "<group>";
118 }; 144 };
119 - FF36E403CAC9E06A5A96BB9F /* Frameworks */ = { 145 + C3326B04728E979C0CAEA680 /* Pods */ = {
120 isa = PBXGroup; 146 isa = PBXGroup;
121 children = ( 147 children = (
122 - 54E006799E73DEAB41FD3623 /* Pods_Runner.framework */, 148 + CB190BADFE09EBDD6E5B44BE /* Pods-Runner.debug.xcconfig */,
  149 + F3732C1D66ACA2383B88CDC2 /* Pods-Runner.release.xcconfig */,
  150 + 337C24E5CFDB233CDBA51D9D /* Pods-Runner.profile.xcconfig */,
  151 + A5A24D61913C510FB38A434E /* Pods-RunnerTests.debug.xcconfig */,
  152 + ADC5B74E7E0BCF65C9CD3AA9 /* Pods-RunnerTests.release.xcconfig */,
  153 + 7F6B8553738879C25774B609 /* Pods-RunnerTests.profile.xcconfig */,
  154 + );
  155 + path = Pods;
  156 + sourceTree = "<group>";
  157 + };
  158 + E31F04DB28A3D0F29210F29D /* Frameworks */ = {
  159 + isa = PBXGroup;
  160 + children = (
  161 + FB50C06655B1959BAA8EAF64 /* Pods_Runner.framework */,
  162 + 9F20A7A668B7797BBB5443C1 /* Pods_RunnerTests.framework */,
123 ); 163 );
124 name = Frameworks; 164 name = Frameworks;
125 sourceTree = "<group>"; 165 sourceTree = "<group>";
@@ -127,18 +167,37 @@ @@ -127,18 +167,37 @@
127 /* End PBXGroup section */ 167 /* End PBXGroup section */
128 168
129 /* Begin PBXNativeTarget section */ 169 /* Begin PBXNativeTarget section */
  170 + 331C8080294A63A400263BE5 /* RunnerTests */ = {
  171 + isa = PBXNativeTarget;
  172 + buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */;
  173 + buildPhases = (
  174 + C7DE006A696F551C4E067E41 /* [CP] Check Pods Manifest.lock */,
  175 + 331C807D294A63A400263BE5 /* Sources */,
  176 + 331C807F294A63A400263BE5 /* Resources */,
  177 + 0F3F7D03C80D20F6F18A5A17 /* Frameworks */,
  178 + );
  179 + buildRules = (
  180 + );
  181 + dependencies = (
  182 + 331C8086294A63A400263BE5 /* PBXTargetDependency */,
  183 + );
  184 + name = RunnerTests;
  185 + productName = RunnerTests;
  186 + productReference = 331C8081294A63A400263BE5 /* RunnerTests.xctest */;
  187 + productType = "com.apple.product-type.bundle.unit-test";
  188 + };
130 97C146ED1CF9000F007C117D /* Runner */ = { 189 97C146ED1CF9000F007C117D /* Runner */ = {
131 isa = PBXNativeTarget; 190 isa = PBXNativeTarget;
132 buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; 191 buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
133 buildPhases = ( 192 buildPhases = (
134 - B086C54F5791A4E759CB6822 /* [CP] Check Pods Manifest.lock */, 193 + C8221F629E52D01B6ABC23B2 /* [CP] Check Pods Manifest.lock */,
135 9740EEB61CF901F6004384FC /* Run Script */, 194 9740EEB61CF901F6004384FC /* Run Script */,
136 97C146EA1CF9000F007C117D /* Sources */, 195 97C146EA1CF9000F007C117D /* Sources */,
137 97C146EB1CF9000F007C117D /* Frameworks */, 196 97C146EB1CF9000F007C117D /* Frameworks */,
138 97C146EC1CF9000F007C117D /* Resources */, 197 97C146EC1CF9000F007C117D /* Resources */,
139 9705A1C41CF9048500538489 /* Embed Frameworks */, 198 9705A1C41CF9048500538489 /* Embed Frameworks */,
140 3B06AD1E1E4923F5004D2608 /* Thin Binary */, 199 3B06AD1E1E4923F5004D2608 /* Thin Binary */,
141 - F825A499E8C466DB9DC6247D /* [CP] Embed Pods Frameworks */, 200 + 3DBCC0215D7BED1D9A756EA3 /* [CP] Embed Pods Frameworks */,
142 ); 201 );
143 buildRules = ( 202 buildRules = (
144 ); 203 );
@@ -155,9 +214,14 @@ @@ -155,9 +214,14 @@
155 97C146E61CF9000F007C117D /* Project object */ = { 214 97C146E61CF9000F007C117D /* Project object */ = {
156 isa = PBXProject; 215 isa = PBXProject;
157 attributes = { 216 attributes = {
158 - LastUpgradeCheck = 1300; 217 + BuildIndependentTargetsInParallel = YES;
  218 + LastUpgradeCheck = 1430;
159 ORGANIZATIONNAME = ""; 219 ORGANIZATIONNAME = "";
160 TargetAttributes = { 220 TargetAttributes = {
  221 + 331C8080294A63A400263BE5 = {
  222 + CreatedOnToolsVersion = 14.0;
  223 + TestTargetID = 97C146ED1CF9000F007C117D;
  224 + };
161 97C146ED1CF9000F007C117D = { 225 97C146ED1CF9000F007C117D = {
162 CreatedOnToolsVersion = 7.3.1; 226 CreatedOnToolsVersion = 7.3.1;
163 LastSwiftMigration = 1100; 227 LastSwiftMigration = 1100;
@@ -178,11 +242,19 @@ @@ -178,11 +242,19 @@
178 projectRoot = ""; 242 projectRoot = "";
179 targets = ( 243 targets = (
180 97C146ED1CF9000F007C117D /* Runner */, 244 97C146ED1CF9000F007C117D /* Runner */,
  245 + 331C8080294A63A400263BE5 /* RunnerTests */,
181 ); 246 );
182 }; 247 };
183 /* End PBXProject section */ 248 /* End PBXProject section */
184 249
185 /* Begin PBXResourcesBuildPhase section */ 250 /* Begin PBXResourcesBuildPhase section */
  251 + 331C807F294A63A400263BE5 /* Resources */ = {
  252 + isa = PBXResourcesBuildPhase;
  253 + buildActionMask = 2147483647;
  254 + files = (
  255 + );
  256 + runOnlyForDeploymentPostprocessing = 0;
  257 + };
186 97C146EC1CF9000F007C117D /* Resources */ = { 258 97C146EC1CF9000F007C117D /* Resources */ = {
187 isa = PBXResourcesBuildPhase; 259 isa = PBXResourcesBuildPhase;
188 buildActionMask = 2147483647; 260 buildActionMask = 2147483647;
@@ -213,6 +285,23 @@ @@ -213,6 +285,23 @@
213 shellPath = /bin/sh; 285 shellPath = /bin/sh;
214 shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; 286 shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin";
215 }; 287 };
  288 + 3DBCC0215D7BED1D9A756EA3 /* [CP] Embed Pods Frameworks */ = {
  289 + isa = PBXShellScriptBuildPhase;
  290 + buildActionMask = 2147483647;
  291 + files = (
  292 + );
  293 + inputFileListPaths = (
  294 + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist",
  295 + );
  296 + name = "[CP] Embed Pods Frameworks";
  297 + outputFileListPaths = (
  298 + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist",
  299 + );
  300 + runOnlyForDeploymentPostprocessing = 0;
  301 + shellPath = /bin/sh;
  302 + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n";
  303 + showEnvVarsInLog = 0;
  304 + };
216 9740EEB61CF901F6004384FC /* Run Script */ = { 305 9740EEB61CF901F6004384FC /* Run Script */ = {
217 isa = PBXShellScriptBuildPhase; 306 isa = PBXShellScriptBuildPhase;
218 alwaysOutOfDate = 1; 307 alwaysOutOfDate = 1;
@@ -228,7 +317,7 @@ @@ -228,7 +317,7 @@
228 shellPath = /bin/sh; 317 shellPath = /bin/sh;
229 shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; 318 shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build";
230 }; 319 };
231 - B086C54F5791A4E759CB6822 /* [CP] Check Pods Manifest.lock */ = { 320 + C7DE006A696F551C4E067E41 /* [CP] Check Pods Manifest.lock */ = {
232 isa = PBXShellScriptBuildPhase; 321 isa = PBXShellScriptBuildPhase;
233 buildActionMask = 2147483647; 322 buildActionMask = 2147483647;
234 files = ( 323 files = (
@@ -243,33 +332,46 @@ @@ -243,33 +332,46 @@
243 outputFileListPaths = ( 332 outputFileListPaths = (
244 ); 333 );
245 outputPaths = ( 334 outputPaths = (
246 - "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", 335 + "$(DERIVED_FILE_DIR)/Pods-RunnerTests-checkManifestLockResult.txt",
247 ); 336 );
248 runOnlyForDeploymentPostprocessing = 0; 337 runOnlyForDeploymentPostprocessing = 0;
249 shellPath = /bin/sh; 338 shellPath = /bin/sh;
250 shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; 339 shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
251 showEnvVarsInLog = 0; 340 showEnvVarsInLog = 0;
252 }; 341 };
253 - F825A499E8C466DB9DC6247D /* [CP] Embed Pods Frameworks */ = { 342 + C8221F629E52D01B6ABC23B2 /* [CP] Check Pods Manifest.lock */ = {
254 isa = PBXShellScriptBuildPhase; 343 isa = PBXShellScriptBuildPhase;
255 buildActionMask = 2147483647; 344 buildActionMask = 2147483647;
256 files = ( 345 files = (
257 ); 346 );
258 inputFileListPaths = ( 347 inputFileListPaths = (
259 - "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist",  
260 ); 348 );
261 - name = "[CP] Embed Pods Frameworks"; 349 + inputPaths = (
  350 + "${PODS_PODFILE_DIR_PATH}/Podfile.lock",
  351 + "${PODS_ROOT}/Manifest.lock",
  352 + );
  353 + name = "[CP] Check Pods Manifest.lock";
262 outputFileListPaths = ( 354 outputFileListPaths = (
263 - "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", 355 + );
  356 + outputPaths = (
  357 + "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt",
264 ); 358 );
265 runOnlyForDeploymentPostprocessing = 0; 359 runOnlyForDeploymentPostprocessing = 0;
266 shellPath = /bin/sh; 360 shellPath = /bin/sh;
267 - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; 361 + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
268 showEnvVarsInLog = 0; 362 showEnvVarsInLog = 0;
269 }; 363 };
270 /* End PBXShellScriptBuildPhase section */ 364 /* End PBXShellScriptBuildPhase section */
271 365
272 /* Begin PBXSourcesBuildPhase section */ 366 /* Begin PBXSourcesBuildPhase section */
  367 + 331C807D294A63A400263BE5 /* Sources */ = {
  368 + isa = PBXSourcesBuildPhase;
  369 + buildActionMask = 2147483647;
  370 + files = (
  371 + 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */,
  372 + );
  373 + runOnlyForDeploymentPostprocessing = 0;
  374 + };
273 97C146EA1CF9000F007C117D /* Sources */ = { 375 97C146EA1CF9000F007C117D /* Sources */ = {
274 isa = PBXSourcesBuildPhase; 376 isa = PBXSourcesBuildPhase;
275 buildActionMask = 2147483647; 377 buildActionMask = 2147483647;
@@ -281,6 +383,14 @@ @@ -281,6 +383,14 @@
281 }; 383 };
282 /* End PBXSourcesBuildPhase section */ 384 /* End PBXSourcesBuildPhase section */
283 385
  386 +/* Begin PBXTargetDependency section */
  387 + 331C8086294A63A400263BE5 /* PBXTargetDependency */ = {
  388 + isa = PBXTargetDependency;
  389 + target = 97C146ED1CF9000F007C117D /* Runner */;
  390 + targetProxy = 331C8085294A63A400263BE5 /* PBXContainerItemProxy */;
  391 + };
  392 +/* End PBXTargetDependency section */
  393 +
284 /* Begin PBXVariantGroup section */ 394 /* Begin PBXVariantGroup section */
285 97C146FA1CF9000F007C117D /* Main.storyboard */ = { 395 97C146FA1CF9000F007C117D /* Main.storyboard */ = {
286 isa = PBXVariantGroup; 396 isa = PBXVariantGroup;
@@ -367,7 +477,7 @@ @@ -367,7 +477,7 @@
367 "$(inherited)", 477 "$(inherited)",
368 "@executable_path/Frameworks", 478 "@executable_path/Frameworks",
369 ); 479 );
370 - PRODUCT_BUNDLE_IDENTIFIER = dev.steenbakker.mobileScannerExample; 480 + PRODUCT_BUNDLE_IDENTIFIER = "com.example.mobile-scanner";
371 PRODUCT_NAME = "$(TARGET_NAME)"; 481 PRODUCT_NAME = "$(TARGET_NAME)";
372 PROVISIONING_PROFILE_SPECIFIER = ""; 482 PROVISIONING_PROFILE_SPECIFIER = "";
373 SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; 483 SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
@@ -376,6 +486,56 @@ @@ -376,6 +486,56 @@
376 }; 486 };
377 name = Profile; 487 name = Profile;
378 }; 488 };
  489 + 331C8088294A63A400263BE5 /* Debug */ = {
  490 + isa = XCBuildConfiguration;
  491 + baseConfigurationReference = A5A24D61913C510FB38A434E /* Pods-RunnerTests.debug.xcconfig */;
  492 + buildSettings = {
  493 + BUNDLE_LOADER = "$(TEST_HOST)";
  494 + CODE_SIGN_STYLE = Automatic;
  495 + CURRENT_PROJECT_VERSION = 1;
  496 + GENERATE_INFOPLIST_FILE = YES;
  497 + MARKETING_VERSION = 1.0;
  498 + PRODUCT_BUNDLE_IDENTIFIER = com.example.mobile-scanner.RunnerTests;
  499 + PRODUCT_NAME = "$(TARGET_NAME)";
  500 + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
  501 + SWIFT_OPTIMIZATION_LEVEL = "-Onone";
  502 + SWIFT_VERSION = 5.0;
  503 + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
  504 + };
  505 + name = Debug;
  506 + };
  507 + 331C8089294A63A400263BE5 /* Release */ = {
  508 + isa = XCBuildConfiguration;
  509 + baseConfigurationReference = ADC5B74E7E0BCF65C9CD3AA9 /* Pods-RunnerTests.release.xcconfig */;
  510 + buildSettings = {
  511 + BUNDLE_LOADER = "$(TEST_HOST)";
  512 + CODE_SIGN_STYLE = Automatic;
  513 + CURRENT_PROJECT_VERSION = 1;
  514 + GENERATE_INFOPLIST_FILE = YES;
  515 + MARKETING_VERSION = 1.0;
  516 + PRODUCT_BUNDLE_IDENTIFIER = com.example.mobile-scanner.RunnerTests;
  517 + PRODUCT_NAME = "$(TARGET_NAME)";
  518 + SWIFT_VERSION = 5.0;
  519 + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
  520 + };
  521 + name = Release;
  522 + };
  523 + 331C808A294A63A400263BE5 /* Profile */ = {
  524 + isa = XCBuildConfiguration;
  525 + baseConfigurationReference = 7F6B8553738879C25774B609 /* Pods-RunnerTests.profile.xcconfig */;
  526 + buildSettings = {
  527 + BUNDLE_LOADER = "$(TEST_HOST)";
  528 + CODE_SIGN_STYLE = Automatic;
  529 + CURRENT_PROJECT_VERSION = 1;
  530 + GENERATE_INFOPLIST_FILE = YES;
  531 + MARKETING_VERSION = 1.0;
  532 + PRODUCT_BUNDLE_IDENTIFIER = com.example.mobile-scanner.RunnerTests;
  533 + PRODUCT_NAME = "$(TARGET_NAME)";
  534 + SWIFT_VERSION = 5.0;
  535 + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
  536 + };
  537 + name = Profile;
  538 + };
379 97C147031CF9000F007C117D /* Debug */ = { 539 97C147031CF9000F007C117D /* Debug */ = {
380 isa = XCBuildConfiguration; 540 isa = XCBuildConfiguration;
381 buildSettings = { 541 buildSettings = {
@@ -499,7 +659,7 @@ @@ -499,7 +659,7 @@
499 "$(inherited)", 659 "$(inherited)",
500 "@executable_path/Frameworks", 660 "@executable_path/Frameworks",
501 ); 661 );
502 - PRODUCT_BUNDLE_IDENTIFIER = dev.steenbakker.mobileScannerExample; 662 + PRODUCT_BUNDLE_IDENTIFIER = "com.example.mobile-scanner";
503 PRODUCT_NAME = "$(TARGET_NAME)"; 663 PRODUCT_NAME = "$(TARGET_NAME)";
504 PROVISIONING_PROFILE_SPECIFIER = ""; 664 PROVISIONING_PROFILE_SPECIFIER = "";
505 SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; 665 SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
@@ -525,7 +685,7 @@ @@ -525,7 +685,7 @@
525 "$(inherited)", 685 "$(inherited)",
526 "@executable_path/Frameworks", 686 "@executable_path/Frameworks",
527 ); 687 );
528 - PRODUCT_BUNDLE_IDENTIFIER = dev.steenbakker.mobileScannerExample; 688 + PRODUCT_BUNDLE_IDENTIFIER = "com.example.mobile-scanner";
529 PRODUCT_NAME = "$(TARGET_NAME)"; 689 PRODUCT_NAME = "$(TARGET_NAME)";
530 PROVISIONING_PROFILE_SPECIFIER = ""; 690 PROVISIONING_PROFILE_SPECIFIER = "";
531 SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; 691 SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
@@ -537,6 +697,16 @@ @@ -537,6 +697,16 @@
537 /* End XCBuildConfiguration section */ 697 /* End XCBuildConfiguration section */
538 698
539 /* Begin XCConfigurationList section */ 699 /* Begin XCConfigurationList section */
  700 + 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = {
  701 + isa = XCConfigurationList;
  702 + buildConfigurations = (
  703 + 331C8088294A63A400263BE5 /* Debug */,
  704 + 331C8089294A63A400263BE5 /* Release */,
  705 + 331C808A294A63A400263BE5 /* Profile */,
  706 + );
  707 + defaultConfigurationIsVisible = 0;
  708 + defaultConfigurationName = Release;
  709 + };
540 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { 710 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = {
541 isa = XCConfigurationList; 711 isa = XCConfigurationList;
542 buildConfigurations = ( 712 buildConfigurations = (
1 <?xml version="1.0" encoding="UTF-8"?> 1 <?xml version="1.0" encoding="UTF-8"?>
2 <Scheme 2 <Scheme
3 - LastUpgradeVersion = "1300" 3 + LastUpgradeVersion = "1430"
4 version = "1.3"> 4 version = "1.3">
5 <BuildAction 5 <BuildAction
6 parallelizeBuildables = "YES" 6 parallelizeBuildables = "YES"
@@ -37,6 +37,17 @@ @@ -37,6 +37,17 @@
37 </BuildableReference> 37 </BuildableReference>
38 </MacroExpansion> 38 </MacroExpansion>
39 <Testables> 39 <Testables>
  40 + <TestableReference
  41 + skipped = "NO"
  42 + parallelizable = "YES">
  43 + <BuildableReference
  44 + BuildableIdentifier = "primary"
  45 + BlueprintIdentifier = "331C8080294A63A400263BE5"
  46 + BuildableName = "RunnerTests.xctest"
  47 + BlueprintName = "RunnerTests"
  48 + ReferencedContainer = "container:Runner.xcodeproj">
  49 + </BuildableReference>
  50 + </TestableReference>
40 </Testables> 51 </Testables>
41 </TestAction> 52 </TestAction>
42 <LaunchAction 53 <LaunchAction
@@ -2,8 +2,6 @@ @@ -2,8 +2,6 @@
2 <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> 2 <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3 <plist version="1.0"> 3 <plist version="1.0">
4 <dict> 4 <dict>
5 - <key>CADisableMinimumFrameDurationOnPhone</key>  
6 - <true/>  
7 <key>CFBundleDevelopmentRegion</key> 5 <key>CFBundleDevelopmentRegion</key>
8 <string>$(DEVELOPMENT_LANGUAGE)</string> 6 <string>$(DEVELOPMENT_LANGUAGE)</string>
9 <key>CFBundleDisplayName</key> 7 <key>CFBundleDisplayName</key>
@@ -27,11 +25,9 @@ @@ -27,11 +25,9 @@
27 <key>LSRequiresIPhoneOS</key> 25 <key>LSRequiresIPhoneOS</key>
28 <true/> 26 <true/>
29 <key>NSCameraUsageDescription</key> 27 <key>NSCameraUsageDescription</key>
30 - <string>We use the camera to scan barcodes</string> 28 + <string>This app needs camera access to scan QR codes</string>
31 <key>NSPhotoLibraryUsageDescription</key> 29 <key>NSPhotoLibraryUsageDescription</key>
32 - <string>We need access in order to open photos of barcodes</string>  
33 - <key>UIApplicationSupportsIndirectInputEvents</key>  
34 - <true/> 30 + <string>This app needs photos access to get QR code from photo library</string>
35 <key>UILaunchStoryboardName</key> 31 <key>UILaunchStoryboardName</key>
36 <string>LaunchScreen</string> 32 <string>LaunchScreen</string>
37 <key>UIMainStoryboardFile</key> 33 <key>UIMainStoryboardFile</key>
@@ -51,5 +47,9 @@ @@ -51,5 +47,9 @@
51 </array> 47 </array>
52 <key>UIViewControllerBasedStatusBarAppearance</key> 48 <key>UIViewControllerBasedStatusBarAppearance</key>
53 <false/> 49 <false/>
  50 + <key>CADisableMinimumFrameDurationOnPhone</key>
  51 + <true/>
  52 + <key>UIApplicationSupportsIndirectInputEvents</key>
  53 + <true/>
54 </dict> 54 </dict>
55 </plist> 55 </plist>
  1 +import Flutter
  2 +import UIKit
  3 +import XCTest
  4 +
  5 +@testable import mobile_scanner
  6 +
  7 +// This demonstrates a simple unit test of the Swift portion of this plugin's implementation.
  8 +//
  9 +// See https://developer.apple.com/documentation/xctest for more information about using XCTest.
  10 +
  11 +class RunnerTests: XCTestCase {
  12 +
  13 + // TODO: this test was left as-is from the template, but it obviuosly fails for now.
  14 + // Add new tests later.
  15 + /*
  16 + func testGetPlatformVersion() {
  17 + let plugin = MobileScannerPlugin()
  18 +
  19 + let call = FlutterMethodCall(methodName: "getPlatformVersion", arguments: [])
  20 +
  21 + let resultExpectation = expectation(description: "result block must be called.")
  22 + plugin.handle(call) { result in
  23 + XCTAssertEqual(result as! String, "iOS " + UIDevice.current.systemVersion)
  24 + resultExpectation.fulfill()
  25 + }
  26 + waitForExpectations(timeout: 1)
  27 + }*/
  28 +
  29 +}
@@ -4,10 +4,10 @@ import 'package:mobile_scanner/mobile_scanner.dart'; @@ -4,10 +4,10 @@ import 'package:mobile_scanner/mobile_scanner.dart';
4 import 'package:mobile_scanner_example/scanner_error_widget.dart'; 4 import 'package:mobile_scanner_example/scanner_error_widget.dart';
5 5
6 class BarcodeListScannerWithController extends StatefulWidget { 6 class BarcodeListScannerWithController extends StatefulWidget {
7 - const BarcodeListScannerWithController({Key? key}) : super(key: key); 7 + const BarcodeListScannerWithController({super.key});
8 8
9 @override 9 @override
10 - _BarcodeListScannerWithControllerState createState() => 10 + State<BarcodeListScannerWithController> createState() =>
11 _BarcodeListScannerWithControllerState(); 11 _BarcodeListScannerWithControllerState();
12 } 12 }
13 13
@@ -82,16 +82,10 @@ class _BarcodeListScannerWithControllerState @@ -82,16 +82,10 @@ class _BarcodeListScannerWithControllerState
82 children: [ 82 children: [
83 IconButton( 83 IconButton(
84 color: Colors.white, 84 color: Colors.white,
85 - icon: ValueListenableBuilder( 85 + icon: ValueListenableBuilder<TorchState>(
86 valueListenable: controller.torchState, 86 valueListenable: controller.torchState,
87 builder: (context, state, child) { 87 builder: (context, state, child) {
88 - if (state == null) {  
89 - return const Icon(  
90 - Icons.flash_off,  
91 - color: Colors.grey,  
92 - );  
93 - }  
94 - switch (state as TorchState) { 88 + switch (state) {
95 case TorchState.off: 89 case TorchState.off:
96 return const Icon( 90 return const Icon(
97 Icons.flash_off, 91 Icons.flash_off,
@@ -134,13 +128,10 @@ class _BarcodeListScannerWithControllerState @@ -134,13 +128,10 @@ class _BarcodeListScannerWithControllerState
134 ), 128 ),
135 IconButton( 129 IconButton(
136 color: Colors.white, 130 color: Colors.white,
137 - icon: ValueListenableBuilder( 131 + icon: ValueListenableBuilder<CameraFacing>(
138 valueListenable: controller.cameraFacingState, 132 valueListenable: controller.cameraFacingState,
139 builder: (context, state, child) { 133 builder: (context, state, child) {
140 - if (state == null) {  
141 - return const Icon(Icons.camera_front);  
142 - }  
143 - switch (state as CameraFacing) { 134 + switch (state) {
144 case CameraFacing.front: 135 case CameraFacing.front:
145 return const Icon(Icons.camera_front); 136 return const Icon(Icons.camera_front);
146 case CameraFacing.back: 137 case CameraFacing.back:
@@ -4,10 +4,10 @@ import 'package:mobile_scanner/mobile_scanner.dart'; @@ -4,10 +4,10 @@ import 'package:mobile_scanner/mobile_scanner.dart';
4 import 'package:mobile_scanner_example/scanner_error_widget.dart'; 4 import 'package:mobile_scanner_example/scanner_error_widget.dart';
5 5
6 class BarcodeScannerWithController extends StatefulWidget { 6 class BarcodeScannerWithController extends StatefulWidget {
7 - const BarcodeScannerWithController({Key? key}) : super(key: key); 7 + const BarcodeScannerWithController({super.key});
8 8
9 @override 9 @override
10 - _BarcodeScannerWithControllerState createState() => 10 + State<BarcodeScannerWithController> createState() =>
11 _BarcodeScannerWithControllerState(); 11 _BarcodeScannerWithControllerState();
12 } 12 }
13 13
@@ -17,7 +17,7 @@ class _BarcodeScannerWithControllerState @@ -17,7 +17,7 @@ class _BarcodeScannerWithControllerState
17 BarcodeCapture? barcode; 17 BarcodeCapture? barcode;
18 18
19 final MobileScannerController controller = MobileScannerController( 19 final MobileScannerController controller = MobileScannerController(
20 - torchEnabled: true, 20 + torchEnabled: true, useNewCameraSelector: true,
21 // formats: [BarcodeFormat.qrCode] 21 // formats: [BarcodeFormat.qrCode]
22 // facing: CameraFacing.front, 22 // facing: CameraFacing.front,
23 // detectionSpeed: DetectionSpeed.normal 23 // detectionSpeed: DetectionSpeed.normal
@@ -47,6 +47,8 @@ class _BarcodeScannerWithControllerState @@ -47,6 +47,8 @@ class _BarcodeScannerWithControllerState
47 } 47 }
48 } 48 }
49 49
  50 + int? nrOfCameras;
  51 +
50 @override 52 @override
51 Widget build(BuildContext context) { 53 Widget build(BuildContext context) {
52 return Scaffold( 54 return Scaffold(
@@ -57,6 +59,12 @@ class _BarcodeScannerWithControllerState @@ -57,6 +59,12 @@ class _BarcodeScannerWithControllerState
57 return Stack( 59 return Stack(
58 children: [ 60 children: [
59 MobileScanner( 61 MobileScanner(
  62 + onScannerStarted: (arguments) {
  63 + if (arguments?.nrOfCameras != null) {
  64 + nrOfCameras = arguments!.nrOfCameras;
  65 + setState(() {});
  66 + }
  67 + },
60 controller: controller, 68 controller: controller,
61 errorBuilder: (context, error, child) { 69 errorBuilder: (context, error, child) {
62 return ScannerErrorWidget(error: error); 70 return ScannerErrorWidget(error: error);
@@ -85,16 +93,10 @@ class _BarcodeScannerWithControllerState @@ -85,16 +93,10 @@ class _BarcodeScannerWithControllerState
85 } 93 }
86 return IconButton( 94 return IconButton(
87 color: Colors.white, 95 color: Colors.white,
88 - icon: ValueListenableBuilder( 96 + icon: ValueListenableBuilder<TorchState>(
89 valueListenable: controller.torchState, 97 valueListenable: controller.torchState,
90 builder: (context, state, child) { 98 builder: (context, state, child) {
91 - if (state == null) {  
92 - return const Icon(  
93 - Icons.flash_off,  
94 - color: Colors.grey,  
95 - );  
96 - }  
97 - switch (state as TorchState) { 99 + switch (state) {
98 case TorchState.off: 100 case TorchState.off:
99 return const Icon( 101 return const Icon(
100 Icons.flash_off, 102 Icons.flash_off,
@@ -140,13 +142,10 @@ class _BarcodeScannerWithControllerState @@ -140,13 +142,10 @@ class _BarcodeScannerWithControllerState
140 ), 142 ),
141 IconButton( 143 IconButton(
142 color: Colors.white, 144 color: Colors.white,
143 - icon: ValueListenableBuilder( 145 + icon: ValueListenableBuilder<CameraFacing>(
144 valueListenable: controller.cameraFacingState, 146 valueListenable: controller.cameraFacingState,
145 builder: (context, state, child) { 147 builder: (context, state, child) {
146 - if (state == null) {  
147 - return const Icon(Icons.camera_front);  
148 - }  
149 - switch (state as CameraFacing) { 148 + switch (state) {
150 case CameraFacing.front: 149 case CameraFacing.front:
151 return const Icon(Icons.camera_front); 150 return const Icon(Icons.camera_front);
152 case CameraFacing.back: 151 case CameraFacing.back:
@@ -155,7 +154,9 @@ class _BarcodeScannerWithControllerState @@ -155,7 +154,9 @@ class _BarcodeScannerWithControllerState
155 }, 154 },
156 ), 155 ),
157 iconSize: 32.0, 156 iconSize: 32.0,
158 - onPressed: () => controller.switchCamera(), 157 + onPressed: nrOfCameras != null && nrOfCameras! < 2
  158 + ? null
  159 + : () => controller.switchCamera(),
159 ), 160 ),
160 IconButton( 161 IconButton(
161 color: Colors.white, 162 color: Colors.white,
@@ -3,10 +3,10 @@ import 'package:mobile_scanner/mobile_scanner.dart'; @@ -3,10 +3,10 @@ import 'package:mobile_scanner/mobile_scanner.dart';
3 import 'package:mobile_scanner_example/scanner_error_widget.dart'; 3 import 'package:mobile_scanner_example/scanner_error_widget.dart';
4 4
5 class BarcodeScannerPageView extends StatefulWidget { 5 class BarcodeScannerPageView extends StatefulWidget {
6 - const BarcodeScannerPageView({Key? key}) : super(key: key); 6 + const BarcodeScannerPageView({super.key});
7 7
8 @override 8 @override
9 - _BarcodeScannerPageViewState createState() => _BarcodeScannerPageViewState(); 9 + State<BarcodeScannerPageView> createState() => _BarcodeScannerPageViewState();
10 } 10 }
11 11
12 class _BarcodeScannerPageViewState extends State<BarcodeScannerPageView> 12 class _BarcodeScannerPageViewState extends State<BarcodeScannerPageView>
@@ -5,10 +5,10 @@ import 'package:mobile_scanner/mobile_scanner.dart'; @@ -5,10 +5,10 @@ import 'package:mobile_scanner/mobile_scanner.dart';
5 import 'package:mobile_scanner_example/scanner_error_widget.dart'; 5 import 'package:mobile_scanner_example/scanner_error_widget.dart';
6 6
7 class BarcodeScannerReturningImage extends StatefulWidget { 7 class BarcodeScannerReturningImage extends StatefulWidget {
8 - const BarcodeScannerReturningImage({Key? key}) : super(key: key); 8 + const BarcodeScannerReturningImage({super.key});
9 9
10 @override 10 @override
11 - _BarcodeScannerReturningImageState createState() => 11 + State<BarcodeScannerReturningImage> createState() =>
12 _BarcodeScannerReturningImageState(); 12 _BarcodeScannerReturningImageState();
13 } 13 }
14 14
@@ -16,7 +16,7 @@ class _BarcodeScannerReturningImageState @@ -16,7 +16,7 @@ class _BarcodeScannerReturningImageState
16 extends State<BarcodeScannerReturningImage> 16 extends State<BarcodeScannerReturningImage>
17 with SingleTickerProviderStateMixin { 17 with SingleTickerProviderStateMixin {
18 BarcodeCapture? barcode; 18 BarcodeCapture? barcode;
19 - MobileScannerArguments? arguments; 19 + // MobileScannerArguments? arguments;
20 20
21 final MobileScannerController controller = MobileScannerController( 21 final MobileScannerController controller = MobileScannerController(
22 torchEnabled: true, 22 torchEnabled: true,
@@ -101,16 +101,10 @@ class _BarcodeScannerReturningImageState @@ -101,16 +101,10 @@ class _BarcodeScannerReturningImageState
101 children: [ 101 children: [
102 IconButton( 102 IconButton(
103 color: Colors.white, 103 color: Colors.white,
104 - icon: ValueListenableBuilder( 104 + icon: ValueListenableBuilder<TorchState>(
105 valueListenable: controller.torchState, 105 valueListenable: controller.torchState,
106 builder: (context, state, child) { 106 builder: (context, state, child) {
107 - if (state == null) {  
108 - return const Icon(  
109 - Icons.flash_off,  
110 - color: Colors.grey,  
111 - );  
112 - }  
113 - switch (state as TorchState) { 107 + switch (state) {
114 case TorchState.off: 108 case TorchState.off:
115 return const Icon( 109 return const Icon(
116 Icons.flash_off, 110 Icons.flash_off,
@@ -154,13 +148,10 @@ class _BarcodeScannerReturningImageState @@ -154,13 +148,10 @@ class _BarcodeScannerReturningImageState
154 ), 148 ),
155 IconButton( 149 IconButton(
156 color: Colors.white, 150 color: Colors.white,
157 - icon: ValueListenableBuilder( 151 + icon: ValueListenableBuilder<CameraFacing>(
158 valueListenable: controller.cameraFacingState, 152 valueListenable: controller.cameraFacingState,
159 builder: (context, state, child) { 153 builder: (context, state, child) {
160 - if (state == null) {  
161 - return const Icon(Icons.camera_front);  
162 - }  
163 - switch (state as CameraFacing) { 154 + switch (state) {
164 case CameraFacing.front: 155 case CameraFacing.front:
165 return const Icon(Icons.camera_front); 156 return const Icon(Icons.camera_front);
166 case CameraFacing.back: 157 case CameraFacing.back:
1 import 'dart:io'; 1 import 'dart:io';
2 2
  3 +import 'package:flutter/foundation.dart';
3 import 'package:flutter/material.dart'; 4 import 'package:flutter/material.dart';
4 import 'package:mobile_scanner/mobile_scanner.dart'; 5 import 'package:mobile_scanner/mobile_scanner.dart';
5 6
6 import 'package:mobile_scanner_example/scanner_error_widget.dart'; 7 import 'package:mobile_scanner_example/scanner_error_widget.dart';
7 8
8 class BarcodeScannerWithScanWindow extends StatefulWidget { 9 class BarcodeScannerWithScanWindow extends StatefulWidget {
9 - const BarcodeScannerWithScanWindow({Key? key}) : super(key: key); 10 + const BarcodeScannerWithScanWindow({super.key});
10 11
11 @override 12 @override
12 - _BarcodeScannerWithScanWindowState createState() => 13 + State<BarcodeScannerWithScanWindow> createState() =>
13 _BarcodeScannerWithScanWindowState(); 14 _BarcodeScannerWithScanWindowState();
14 } 15 }
15 16
@@ -150,7 +151,10 @@ class BarcodeOverlay extends CustomPainter { @@ -150,7 +151,10 @@ class BarcodeOverlay extends CustomPainter {
150 151
151 @override 152 @override
152 void paint(Canvas canvas, Size size) { 153 void paint(Canvas canvas, Size size) {
153 - if (barcode.corners == null) return; 154 + if (barcode.corners.isEmpty) {
  155 + return;
  156 + }
  157 +
154 final adjustedSize = applyBoxFit(boxFit, arguments.size, size); 158 final adjustedSize = applyBoxFit(boxFit, arguments.size, size);
155 159
156 double verticalPadding = size.height - adjustedSize.destination.height; 160 double verticalPadding = size.height - adjustedSize.destination.height;
@@ -167,15 +171,19 @@ class BarcodeOverlay extends CustomPainter { @@ -167,15 +171,19 @@ class BarcodeOverlay extends CustomPainter {
167 horizontalPadding = 0; 171 horizontalPadding = 0;
168 } 172 }
169 173
170 - final ratioWidth =  
171 - (Platform.isIOS ? capture.width! : arguments.size.width) /  
172 - adjustedSize.destination.width;  
173 - final ratioHeight =  
174 - (Platform.isIOS ? capture.height! : arguments.size.height) /  
175 - adjustedSize.destination.height; 174 + final double ratioWidth;
  175 + final double ratioHeight;
  176 +
  177 + if (!kIsWeb && Platform.isIOS) {
  178 + ratioWidth = capture.size.width / adjustedSize.destination.width;
  179 + ratioHeight = capture.size.height / adjustedSize.destination.height;
  180 + } else {
  181 + ratioWidth = arguments.size.width / adjustedSize.destination.width;
  182 + ratioHeight = arguments.size.height / adjustedSize.destination.height;
  183 + }
176 184
177 final List<Offset> adjustedOffset = []; 185 final List<Offset> adjustedOffset = [];
178 - for (final offset in barcode.corners!) { 186 + for (final offset in barcode.corners) {
179 adjustedOffset.add( 187 adjustedOffset.add(
180 Offset( 188 Offset(
181 offset.dx / ratioWidth + horizontalPadding, 189 offset.dx / ratioWidth + horizontalPadding,
@@ -3,10 +3,10 @@ import 'package:mobile_scanner/mobile_scanner.dart'; @@ -3,10 +3,10 @@ import 'package:mobile_scanner/mobile_scanner.dart';
3 import 'package:mobile_scanner_example/scanner_error_widget.dart'; 3 import 'package:mobile_scanner_example/scanner_error_widget.dart';
4 4
5 class BarcodeScannerWithoutController extends StatefulWidget { 5 class BarcodeScannerWithoutController extends StatefulWidget {
6 - const BarcodeScannerWithoutController({Key? key}) : super(key: key); 6 + const BarcodeScannerWithoutController({super.key});
7 7
8 @override 8 @override
9 - _BarcodeScannerWithoutControllerState createState() => 9 + State<BarcodeScannerWithoutController> createState() =>
10 _BarcodeScannerWithoutControllerState(); 10 _BarcodeScannerWithoutControllerState();
11 } 11 }
12 12
@@ -5,10 +5,10 @@ import 'package:mobile_scanner/mobile_scanner.dart'; @@ -5,10 +5,10 @@ import 'package:mobile_scanner/mobile_scanner.dart';
5 import 'package:mobile_scanner_example/scanner_error_widget.dart'; 5 import 'package:mobile_scanner_example/scanner_error_widget.dart';
6 6
7 class BarcodeScannerWithZoom extends StatefulWidget { 7 class BarcodeScannerWithZoom extends StatefulWidget {
8 - const BarcodeScannerWithZoom({Key? key}) : super(key: key); 8 + const BarcodeScannerWithZoom({super.key});
9 9
10 @override 10 @override
11 - _BarcodeScannerWithZoomState createState() => _BarcodeScannerWithZoomState(); 11 + State<BarcodeScannerWithZoom> createState() => _BarcodeScannerWithZoomState();
12 } 12 }
13 13
14 class _BarcodeScannerWithZoomState extends State<BarcodeScannerWithZoom> 14 class _BarcodeScannerWithZoomState extends State<BarcodeScannerWithZoom>
@@ -65,16 +65,10 @@ class _BarcodeScannerWithZoomState extends State<BarcodeScannerWithZoom> @@ -65,16 +65,10 @@ class _BarcodeScannerWithZoomState extends State<BarcodeScannerWithZoom>
65 children: [ 65 children: [
66 IconButton( 66 IconButton(
67 color: Colors.white, 67 color: Colors.white,
68 - icon: ValueListenableBuilder( 68 + icon: ValueListenableBuilder<TorchState>(
69 valueListenable: controller.torchState, 69 valueListenable: controller.torchState,
70 builder: (context, state, child) { 70 builder: (context, state, child) {
71 - if (state == null) {  
72 - return const Icon(  
73 - Icons.flash_off,  
74 - color: Colors.grey,  
75 - );  
76 - }  
77 - switch (state as TorchState) { 71 + switch (state) {
78 case TorchState.off: 72 case TorchState.off:
79 return const Icon( 73 return const Icon(
80 Icons.flash_off, 74 Icons.flash_off,
@@ -123,13 +117,10 @@ class _BarcodeScannerWithZoomState extends State<BarcodeScannerWithZoom> @@ -123,13 +117,10 @@ class _BarcodeScannerWithZoomState extends State<BarcodeScannerWithZoom>
123 ), 117 ),
124 IconButton( 118 IconButton(
125 color: Colors.white, 119 color: Colors.white,
126 - icon: ValueListenableBuilder( 120 + icon: ValueListenableBuilder<CameraFacing>(
127 valueListenable: controller.cameraFacingState, 121 valueListenable: controller.cameraFacingState,
128 builder: (context, state, child) { 122 builder: (context, state, child) {
129 - if (state == null) {  
130 - return const Icon(Icons.camera_front);  
131 - }  
132 - switch (state as CameraFacing) { 123 + switch (state) {
133 case CameraFacing.front: 124 case CameraFacing.front:
134 return const Icon(Icons.camera_front); 125 return const Icon(Icons.camera_front);
135 case CameraFacing.back: 126 case CameraFacing.back:
@@ -6,11 +6,12 @@ import 'package:mobile_scanner_example/barcode_scanner_returning_image.dart'; @@ -6,11 +6,12 @@ import 'package:mobile_scanner_example/barcode_scanner_returning_image.dart';
6 import 'package:mobile_scanner_example/barcode_scanner_window.dart'; 6 import 'package:mobile_scanner_example/barcode_scanner_window.dart';
7 import 'package:mobile_scanner_example/barcode_scanner_without_controller.dart'; 7 import 'package:mobile_scanner_example/barcode_scanner_without_controller.dart';
8 import 'package:mobile_scanner_example/barcode_scanner_zoom.dart'; 8 import 'package:mobile_scanner_example/barcode_scanner_zoom.dart';
  9 +import 'package:mobile_scanner_example/mobile_scanner_overlay.dart';
9 10
10 void main() => runApp(const MaterialApp(home: MyHome())); 11 void main() => runApp(const MaterialApp(home: MyHome()));
11 12
12 class MyHome extends StatelessWidget { 13 class MyHome extends StatelessWidget {
13 - const MyHome({Key? key}) : super(key: key); 14 + const MyHome({super.key});
14 15
15 @override 16 @override
16 Widget build(BuildContext context) { 17 Widget build(BuildContext context) {
@@ -95,6 +96,16 @@ class MyHome extends StatelessWidget { @@ -95,6 +96,16 @@ class MyHome extends StatelessWidget {
95 }, 96 },
96 child: const Text('MobileScanner pageView'), 97 child: const Text('MobileScanner pageView'),
97 ), 98 ),
  99 + ElevatedButton(
  100 + onPressed: () {
  101 + Navigator.of(context).push(
  102 + MaterialPageRoute(
  103 + builder: (context) => BarcodeScannerWithOverlay(),
  104 + ),
  105 + );
  106 + },
  107 + child: const Text('MobileScanner with Overlay'),
  108 + ),
98 ], 109 ],
99 ), 110 ),
100 ), 111 ),
1 -//TODO: Create example with scanner overlay  
2 -  
3 -// import 'dart:ui';  
4 -//  
5 -// import 'package:flutter/material.dart';  
6 -// import 'package:mobile_scanner/mobile_scanner.dart';  
7 -//  
8 -// void main() {  
9 -// runApp(const AnalyzeView());  
10 -// }  
11 -//  
12 -// class AnalyzeView extends StatefulWidget {  
13 -// const AnalyzeView({Key? key}) : super(key: key);  
14 -//  
15 -// @override  
16 -// _AnalyzeViewState createState() => _AnalyzeViewState();  
17 -// }  
18 -//  
19 -// class _AnalyzeViewState extends State<AnalyzeView>  
20 -// with SingleTickerProviderStateMixin {  
21 -// List<Offset> points = [];  
22 -//  
23 -// // CameraController cameraController = CameraController(context, width: 320, height: 150);  
24 -//  
25 -// String? barcode;  
26 -//  
27 -// @override  
28 -// Widget build(BuildContext context) {  
29 -// return MaterialApp(  
30 -// home: Scaffold(  
31 -// body: Builder(builder: (context) {  
32 -// return Stack(  
33 -// children: [  
34 -// MobileScanner(  
35 -// // fitScreen: false,  
36 -// // controller: cameraController,  
37 -// onDetect: (barcode, args) {  
38 -// if (this.barcode != barcode.rawValue) {  
39 -// this.barcode = barcode.rawValue;  
40 -// if (barcode.corners != null) {  
41 -// ScaffoldMessenger.of(context).showSnackBar(SnackBar(  
42 -// content: Text(barcode.rawValue),  
43 -// duration: const Duration(milliseconds: 200),  
44 -// animation: null,  
45 -// ));  
46 -// setState(() {  
47 -// final List<Offset> points = [];  
48 -// // double factorWidth = args.size.width / 520;  
49 -// // double factorHeight = wanted / args.size.height;  
50 -// final size = MediaQuery.of(context).devicePixelRatio;  
51 -// debugPrint('Size: ${barcode.corners}');  
52 -// for (var point in barcode.corners!) {  
53 -// final adjustedWith = point.dx;  
54 -// final adjustedHeight = point.dy;  
55 -// points.add(  
56 -// Offset(adjustedWith / size, adjustedHeight / size));  
57 -// // points.add(Offset((point.dx ) / size,  
58 -// // (point.dy) / size));  
59 -// // final differenceWidth = (args.wantedSize!.width - args.size.width) / 2;  
60 -// // final differenceHeight = (args.wantedSize!.height - args.size.height) / 2;  
61 -// // points.add(Offset((point.dx + differenceWidth) / size,  
62 -// // (point.dy + differenceHeight) / size));  
63 -// }  
64 -// this.points = points;  
65 -// });  
66 -// }  
67 -// }  
68 -// // Default 640 x480  
69 -// }),  
70 -// CustomPaint(  
71 -// painter: OpenPainter(points),  
72 -// ),  
73 -// // Container(  
74 -// // alignment: Alignment.bottomCenter,  
75 -// // margin: EdgeInsets.only(bottom: 80.0),  
76 -// // child: IconButton(  
77 -// // icon: ValueListenableBuilder(  
78 -// // valueListenable: cameraController.torchState,  
79 -// // builder: (context, state, child) {  
80 -// // final color =  
81 -// // state == TorchState.off ? Colors.grey : Colors.white;  
82 -// // return Icon(Icons.bolt, color: color);  
83 -// // },  
84 -// // ),  
85 -// // iconSize: 32.0,  
86 -// // onPressed: () => cameraController.torch(),  
87 -// // ),  
88 -// // ),  
89 -// ],  
90 -// );  
91 -// }),  
92 -// ),  
93 -// );  
94 -// }  
95 -//  
96 -// @override  
97 -// void dispose() {  
98 -// // cameraController.dispose();  
99 -// super.dispose();  
100 -// }  
101 -//  
102 -// void display(Barcode barcode) {  
103 -// Navigator.of(context).popAndPushNamed('display', arguments: barcode);  
104 -// }  
105 -// }  
106 -//  
107 -// class OpenPainter extends CustomPainter {  
108 -// final List<Offset> points;  
109 -//  
110 -// OpenPainter(this.points);  
111 -// @override  
112 -// void paint(Canvas canvas, Size size) {  
113 -// var paint1 = Paint()  
114 -// ..color = const Color(0xff63aa65)  
115 -// ..strokeWidth = 10;  
116 -// //draw points on canvas  
117 -// canvas.drawPoints(PointMode.points, points, paint1);  
118 -// }  
119 -//  
120 -// @override  
121 -// bool shouldRepaint(CustomPainter oldDelegate) => true;  
122 -// }  
123 -//  
124 -// class OpacityCurve extends Curve {  
125 -// @override  
126 -// double transform(double t) {  
127 -// if (t < 0.1) {  
128 -// return t * 10;  
129 -// } else if (t <= 0.9) {  
130 -// return 1.0;  
131 -// } else {  
132 -// return (1.0 - t) * 10;  
133 -// }  
134 -// }  
135 -// }  
136 -//  
137 -// // import 'package:flutter/material.dart';  
138 -// // import 'package:flutter/rendering.dart';  
139 -// // import 'package:mobile_scanner/mobile_scanner.dart';  
140 -// //  
141 -// // void main() {  
142 -// // debugPaintSizeEnabled = false;  
143 -// // runApp(HomePage());  
144 -// // }  
145 -// //  
146 -// // class HomePage extends StatefulWidget {  
147 -// // @override  
148 -// // HomeState createState() => HomeState();  
149 -// // }  
150 -// //  
151 -// // class HomeState extends State<HomePage> {  
152 -// // @override  
153 -// // Widget build(BuildContext context) {  
154 -// // return MaterialApp(home: MyApp());  
155 -// // }  
156 -// // }  
157 -// //  
158 -// // class MyApp extends StatefulWidget {  
159 -// // @override  
160 -// // _MyAppState createState() => _MyAppState();  
161 -// // }  
162 -// //  
163 -// // class _MyAppState extends State<MyApp> {  
164 -// // String? qr;  
165 -// // bool camState = false;  
166 -// //  
167 -// // @override  
168 -// // initState() {  
169 -// // super.initState();  
170 -// // }  
171 -// //  
172 -// // @override  
173 -// // Widget build(BuildContext context) {  
174 -// // return Scaffold(  
175 -// // appBar: AppBar(  
176 -// // title: Text('Plugin example app'),  
177 -// // ),  
178 -// // body: Center(  
179 -// // child: Column(  
180 -// // crossAxisAlignment: CrossAxisAlignment.center,  
181 -// // mainAxisAlignment: MainAxisAlignment.center,  
182 -// // children: <Widget>[  
183 -// // Expanded(  
184 -// // child: camState  
185 -// // ? Center(  
186 -// // child: SizedBox(  
187 -// // width: 300.0,  
188 -// // height: 600.0,  
189 -// // child: MobileScanner(  
190 -// // onError: (context, error) => Text(  
191 -// // error.toString(),  
192 -// // style: TextStyle(color: Colors.red),  
193 -// // ),  
194 -// // qrCodeCallback: (code) {  
195 -// // setState(() {  
196 -// // qr = code;  
197 -// // });  
198 -// // },  
199 -// // child: Container(  
200 -// // decoration: BoxDecoration(  
201 -// // color: Colors.transparent,  
202 -// // border: Border.all(  
203 -// // color: Colors.orange,  
204 -// // width: 10.0,  
205 -// // style: BorderStyle.solid),  
206 -// // ),  
207 -// // ),  
208 -// // ),  
209 -// // ),  
210 -// // )  
211 -// // : Center(child: Text("Camera inactive"))),  
212 -// // Text("QRCODE: $qr"),  
213 -// // ],  
214 -// // ),  
215 -// // ),  
216 -// // floatingActionButton: FloatingActionButton(  
217 -// // child: Text(  
218 -// // "press me",  
219 -// // textAlign: TextAlign.center,  
220 -// // ),  
221 -// // onPressed: () {  
222 -// // setState(() {  
223 -// // camState = !camState;  
224 -// // });  
225 -// // }),  
226 -// // );  
227 -// // }  
228 -// // } 1 +import 'package:flutter/material.dart';
  2 +import 'package:mobile_scanner/mobile_scanner.dart';
  3 +import 'package:mobile_scanner_example/scanner_error_widget.dart';
  4 +
  5 +class BarcodeScannerWithOverlay extends StatefulWidget {
  6 + @override
  7 + _BarcodeScannerWithOverlayState createState() =>
  8 + _BarcodeScannerWithOverlayState();
  9 +}
  10 +
  11 +class _BarcodeScannerWithOverlayState extends State<BarcodeScannerWithOverlay> {
  12 + String overlayText = "Please scan QR Code";
  13 + bool camStarted = false;
  14 +
  15 + final MobileScannerController controller = MobileScannerController(
  16 + formats: const [BarcodeFormat.qrCode],
  17 + autoStart: false,
  18 + );
  19 +
  20 + @override
  21 + void dispose() {
  22 + controller.dispose();
  23 + super.dispose();
  24 + }
  25 +
  26 + void startCamera() {
  27 + if (camStarted) {
  28 + return;
  29 + }
  30 +
  31 + controller.start().then((_) {
  32 + if (mounted) {
  33 + setState(() {
  34 + camStarted = true;
  35 + });
  36 + }
  37 + }).catchError((Object error, StackTrace stackTrace) {
  38 + if (mounted) {
  39 + ScaffoldMessenger.of(context).showSnackBar(
  40 + SnackBar(
  41 + content: Text('Something went wrong! $error'),
  42 + backgroundColor: Colors.red,
  43 + ),
  44 + );
  45 + }
  46 + });
  47 + }
  48 +
  49 + void onBarcodeDetect(BarcodeCapture barcodeCapture) {
  50 + final barcode = barcodeCapture.barcodes.last;
  51 + setState(() {
  52 + overlayText = barcodeCapture.barcodes.last.displayValue ??
  53 + barcode.rawValue ??
  54 + 'Barcode has no displayable value';
  55 + });
  56 + }
  57 +
  58 + @override
  59 + Widget build(BuildContext context) {
  60 + final scanWindow = Rect.fromCenter(
  61 + center: MediaQuery.of(context).size.center(Offset.zero),
  62 + width: 200,
  63 + height: 200,
  64 + );
  65 +
  66 + return Scaffold(
  67 + appBar: AppBar(
  68 + title: const Text('Scanner with Overlay Example app'),
  69 + ),
  70 + body: Center(
  71 + child: Column(
  72 + mainAxisAlignment: MainAxisAlignment.center,
  73 + children: <Widget>[
  74 + Expanded(
  75 + child: camStarted
  76 + ? Stack(
  77 + fit: StackFit.expand,
  78 + children: [
  79 + Center(
  80 + child: MobileScanner(
  81 + fit: BoxFit.contain,
  82 + onDetect: onBarcodeDetect,
  83 + overlay: Padding(
  84 + padding: const EdgeInsets.all(16.0),
  85 + child: Align(
  86 + alignment: Alignment.bottomCenter,
  87 + child: Opacity(
  88 + opacity: 0.7,
  89 + child: Text(
  90 + overlayText,
  91 + style: const TextStyle(
  92 + backgroundColor: Colors.black26,
  93 + color: Colors.white,
  94 + fontWeight: FontWeight.bold,
  95 + fontSize: 24,
  96 + overflow: TextOverflow.ellipsis,
  97 + ),
  98 + maxLines: 1,
  99 + ),
  100 + ),
  101 + ),
  102 + ),
  103 + controller: controller,
  104 + scanWindow: scanWindow,
  105 + errorBuilder: (context, error, child) {
  106 + return ScannerErrorWidget(error: error);
  107 + },
  108 + ),
  109 + ),
  110 + CustomPaint(
  111 + painter: ScannerOverlay(scanWindow),
  112 + ),
  113 + Padding(
  114 + padding: const EdgeInsets.all(16.0),
  115 + child: Align(
  116 + alignment: Alignment.bottomCenter,
  117 + child: Row(
  118 + mainAxisAlignment: MainAxisAlignment.spaceEvenly,
  119 + children: [
  120 + ValueListenableBuilder<TorchState>(
  121 + valueListenable: controller.torchState,
  122 + builder: (context, value, child) {
  123 + final Color iconColor;
  124 +
  125 + switch (value) {
  126 + case TorchState.off:
  127 + iconColor = Colors.black;
  128 + break;
  129 + case TorchState.on:
  130 + iconColor = Colors.yellow;
  131 + break;
  132 + }
  133 +
  134 + return IconButton(
  135 + onPressed: () => controller.toggleTorch(),
  136 + icon: Icon(
  137 + Icons.flashlight_on,
  138 + color: iconColor,
  139 + ),
  140 + );
  141 + },
  142 + ),
  143 + IconButton(
  144 + onPressed: () => controller.switchCamera(),
  145 + icon: const Icon(
  146 + Icons.cameraswitch_rounded,
  147 + color: Colors.white,
  148 + ),
  149 + ),
  150 + ],
  151 + ),
  152 + ),
  153 + ),
  154 + ],
  155 + )
  156 + : const Center(
  157 + child: Text("Tap on Camera to activate QR Scanner"),
  158 + ),
  159 + ),
  160 + ],
  161 + ),
  162 + ),
  163 + floatingActionButton: camStarted
  164 + ? null
  165 + : FloatingActionButton(
  166 + onPressed: startCamera,
  167 + child: const Icon(Icons.camera_alt),
  168 + ),
  169 + );
  170 + }
  171 +}
  172 +
  173 +class ScannerOverlay extends CustomPainter {
  174 + ScannerOverlay(this.scanWindow);
  175 +
  176 + final Rect scanWindow;
  177 + final double borderRadius = 12.0;
  178 +
  179 + @override
  180 + void paint(Canvas canvas, Size size) {
  181 + final backgroundPath = Path()..addRect(Rect.largest);
  182 + final cutoutPath = Path()
  183 + ..addRRect(
  184 + RRect.fromRectAndCorners(
  185 + scanWindow,
  186 + topLeft: Radius.circular(borderRadius),
  187 + topRight: Radius.circular(borderRadius),
  188 + bottomLeft: Radius.circular(borderRadius),
  189 + bottomRight: Radius.circular(borderRadius),
  190 + ),
  191 + );
  192 +
  193 + final backgroundPaint = Paint()
  194 + ..color = Colors.black.withOpacity(0.5)
  195 + ..style = PaintingStyle.fill
  196 + ..blendMode = BlendMode.dstOut;
  197 +
  198 + final backgroundWithCutout = Path.combine(
  199 + PathOperation.difference,
  200 + backgroundPath,
  201 + cutoutPath,
  202 + );
  203 +
  204 + // Create a Paint object for the white border
  205 + final borderPaint = Paint()
  206 + ..color = Colors.white
  207 + ..style = PaintingStyle.stroke
  208 + ..strokeWidth = 4.0; // Adjust the border width as needed
  209 +
  210 + // Calculate the border rectangle with rounded corners
  211 +// Adjust the radius as needed
  212 + final borderRect = RRect.fromRectAndCorners(
  213 + scanWindow,
  214 + topLeft: Radius.circular(borderRadius),
  215 + topRight: Radius.circular(borderRadius),
  216 + bottomLeft: Radius.circular(borderRadius),
  217 + bottomRight: Radius.circular(borderRadius),
  218 + );
  219 +
  220 + // Draw the white border
  221 + canvas.drawPath(backgroundWithCutout, backgroundPaint);
  222 + canvas.drawRRect(borderRect, borderPaint);
  223 + }
  224 +
  225 + @override
  226 + bool shouldRepaint(covariant CustomPainter oldDelegate) {
  227 + return false;
  228 + }
  229 +}
@@ -2,7 +2,7 @@ import 'package:flutter/material.dart'; @@ -2,7 +2,7 @@ import 'package:flutter/material.dart';
2 import 'package:mobile_scanner/mobile_scanner.dart'; 2 import 'package:mobile_scanner/mobile_scanner.dart';
3 3
4 class ScannerErrorWidget extends StatelessWidget { 4 class ScannerErrorWidget extends StatelessWidget {
5 - const ScannerErrorWidget({Key? key, required this.error}) : super(key: key); 5 + const ScannerErrorWidget({super.key, required this.error});
6 6
7 final MobileScannerException error; 7 final MobileScannerException error;
8 8
@@ -31,6 +31,9 @@ target 'Runner' do @@ -31,6 +31,9 @@ target 'Runner' do
31 use_modular_headers! 31 use_modular_headers!
32 32
33 flutter_install_all_macos_pods File.dirname(File.realpath(__FILE__)) 33 flutter_install_all_macos_pods File.dirname(File.realpath(__FILE__))
  34 + target 'RunnerTests' do
  35 + inherit! :search_paths
  36 + end
34 end 37 end
35 38
36 post_install do |installer| 39 post_install do |installer|
@@ -21,15 +21,24 @@ @@ -21,15 +21,24 @@
21 /* End PBXAggregateTarget section */ 21 /* End PBXAggregateTarget section */
22 22
23 /* Begin PBXBuildFile section */ 23 /* Begin PBXBuildFile section */
  24 + 331C80D8294CF71000263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C80D7294CF71000263BE5 /* RunnerTests.swift */; };
24 335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */ = {isa = PBXBuildFile; fileRef = 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */; }; 25 335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */ = {isa = PBXBuildFile; fileRef = 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */; };
25 33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC10F02044A3C60003C045 /* AppDelegate.swift */; }; 26 33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC10F02044A3C60003C045 /* AppDelegate.swift */; };
26 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F22044A3C60003C045 /* Assets.xcassets */; }; 27 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F22044A3C60003C045 /* Assets.xcassets */; };
27 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F42044A3C60003C045 /* MainMenu.xib */; }; 28 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F42044A3C60003C045 /* MainMenu.xib */; };
28 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */; }; 29 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */; };
29 - 5348E36EDC155A01222C3599 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 41950513928B2DA794C685E3 /* Pods_Runner.framework */; }; 30 + 58A22AC50A792ECA6D027507 /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 419C3BC9593F6DE903D740F0 /* Pods_RunnerTests.framework */; };
  31 + F209F1436A19CBC32BFFB26A /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = EEB40B96A6FFC92607527710 /* Pods_Runner.framework */; };
30 /* End PBXBuildFile section */ 32 /* End PBXBuildFile section */
31 33
32 /* Begin PBXContainerItemProxy section */ 34 /* Begin PBXContainerItemProxy section */
  35 + 331C80D9294CF71000263BE5 /* PBXContainerItemProxy */ = {
  36 + isa = PBXContainerItemProxy;
  37 + containerPortal = 33CC10E52044A3C60003C045 /* Project object */;
  38 + proxyType = 1;
  39 + remoteGlobalIDString = 33CC10EC2044A3C60003C045;
  40 + remoteInfo = Runner;
  41 + };
33 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */ = { 42 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */ = {
34 isa = PBXContainerItemProxy; 43 isa = PBXContainerItemProxy;
35 containerPortal = 33CC10E52044A3C60003C045 /* Project object */; 44 containerPortal = 33CC10E52044A3C60003C045 /* Project object */;
@@ -53,6 +62,9 @@ @@ -53,6 +62,9 @@
53 /* End PBXCopyFilesBuildPhase section */ 62 /* End PBXCopyFilesBuildPhase section */
54 63
55 /* Begin PBXFileReference section */ 64 /* Begin PBXFileReference section */
  65 + 23E216AF8BD40EB8E52863BE /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = "<group>"; };
  66 + 331C80D5294CF71000263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
  67 + 331C80D7294CF71000263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = "<group>"; };
56 333000ED22D3DE5D00554162 /* Warnings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Warnings.xcconfig; sourceTree = "<group>"; }; 68 333000ED22D3DE5D00554162 /* Warnings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Warnings.xcconfig; sourceTree = "<group>"; };
57 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneratedPluginRegistrant.swift; sourceTree = "<group>"; }; 69 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneratedPluginRegistrant.swift; sourceTree = "<group>"; };
58 33CC10ED2044A3C60003C045 /* mobile_scanner_example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = mobile_scanner_example.app; sourceTree = BUILT_PRODUCTS_DIR; }; 70 33CC10ED2044A3C60003C045 /* mobile_scanner_example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = mobile_scanner_example.app; sourceTree = BUILT_PRODUCTS_DIR; };
@@ -67,34 +79,43 @@ @@ -67,34 +79,43 @@
67 33E51913231747F40026EE4D /* DebugProfile.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = DebugProfile.entitlements; sourceTree = "<group>"; }; 79 33E51913231747F40026EE4D /* DebugProfile.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = DebugProfile.entitlements; sourceTree = "<group>"; };
68 33E51914231749380026EE4D /* Release.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = Release.entitlements; sourceTree = "<group>"; }; 80 33E51914231749380026EE4D /* Release.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = Release.entitlements; sourceTree = "<group>"; };
69 33E5194F232828860026EE4D /* AppInfo.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = AppInfo.xcconfig; sourceTree = "<group>"; }; 81 33E5194F232828860026EE4D /* AppInfo.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = AppInfo.xcconfig; sourceTree = "<group>"; };
70 - 41950513928B2DA794C685E3 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; };  
71 - 433FCBF2B1E3F653F96B3C79 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = "<group>"; }; 82 + 419C3BC9593F6DE903D740F0 /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
72 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = "<group>"; }; 83 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = "<group>"; };
  84 + 8201964090D9F78BC65FF5D2 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = "<group>"; };
73 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = "<group>"; }; 85 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = "<group>"; };
74 - 97D4F0103EE99761EF216A9C /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = "<group>"; };  
75 - B67CB61EED45BF13A197D997 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = "<group>"; }; 86 + 9AB9A83BADC7F755AA147D15 /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = "<group>"; };
  87 + AA0CC45DA8C7F4A77CA80A2E /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = "<group>"; };
  88 + CAEA4D71BCF9158847C4F6AC /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = "<group>"; };
  89 + E1DC1D18B8C13B1C75763506 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = "<group>"; };
  90 + EEB40B96A6FFC92607527710 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; };
76 /* End PBXFileReference section */ 91 /* End PBXFileReference section */
77 92
78 /* Begin PBXFrameworksBuildPhase section */ 93 /* Begin PBXFrameworksBuildPhase section */
  94 + 331C80D2294CF70F00263BE5 /* Frameworks */ = {
  95 + isa = PBXFrameworksBuildPhase;
  96 + buildActionMask = 2147483647;
  97 + files = (
  98 + 58A22AC50A792ECA6D027507 /* Pods_RunnerTests.framework in Frameworks */,
  99 + );
  100 + runOnlyForDeploymentPostprocessing = 0;
  101 + };
79 33CC10EA2044A3C60003C045 /* Frameworks */ = { 102 33CC10EA2044A3C60003C045 /* Frameworks */ = {
80 isa = PBXFrameworksBuildPhase; 103 isa = PBXFrameworksBuildPhase;
81 buildActionMask = 2147483647; 104 buildActionMask = 2147483647;
82 files = ( 105 files = (
83 - 5348E36EDC155A01222C3599 /* Pods_Runner.framework in Frameworks */, 106 + F209F1436A19CBC32BFFB26A /* Pods_Runner.framework in Frameworks */,
84 ); 107 );
85 runOnlyForDeploymentPostprocessing = 0; 108 runOnlyForDeploymentPostprocessing = 0;
86 }; 109 };
87 /* End PBXFrameworksBuildPhase section */ 110 /* End PBXFrameworksBuildPhase section */
88 111
89 /* Begin PBXGroup section */ 112 /* Begin PBXGroup section */
90 - 20F8C9AA20C2A495C125E194 /* Pods */ = { 113 + 331C80D6294CF71000263BE5 /* RunnerTests */ = {
91 isa = PBXGroup; 114 isa = PBXGroup;
92 children = ( 115 children = (
93 - B67CB61EED45BF13A197D997 /* Pods-Runner.debug.xcconfig */,  
94 - 97D4F0103EE99761EF216A9C /* Pods-Runner.release.xcconfig */,  
95 - 433FCBF2B1E3F653F96B3C79 /* Pods-Runner.profile.xcconfig */, 116 + 331C80D7294CF71000263BE5 /* RunnerTests.swift */,
96 ); 117 );
97 - path = Pods; 118 + path = RunnerTests;
98 sourceTree = "<group>"; 119 sourceTree = "<group>";
99 }; 120 };
100 33BA886A226E78AF003329D5 /* Configs */ = { 121 33BA886A226E78AF003329D5 /* Configs */ = {
@@ -113,9 +134,10 @@ @@ -113,9 +134,10 @@
113 children = ( 134 children = (
114 33FAB671232836740065AC1E /* Runner */, 135 33FAB671232836740065AC1E /* Runner */,
115 33CEB47122A05771004F2AC0 /* Flutter */, 136 33CEB47122A05771004F2AC0 /* Flutter */,
  137 + 331C80D6294CF71000263BE5 /* RunnerTests */,
116 33CC10EE2044A3C60003C045 /* Products */, 138 33CC10EE2044A3C60003C045 /* Products */,
117 - 20F8C9AA20C2A495C125E194 /* Pods */,  
118 - 64FFE901A03ED70F67D8DCD6 /* Frameworks */, 139 + D73912EC22F37F3D000D13A0 /* Frameworks */,
  140 + 48C702343BA8440B9F0C4C3B /* Pods */,
119 ); 141 );
120 sourceTree = "<group>"; 142 sourceTree = "<group>";
121 }; 143 };
@@ -123,6 +145,7 @@ @@ -123,6 +145,7 @@
123 isa = PBXGroup; 145 isa = PBXGroup;
124 children = ( 146 children = (
125 33CC10ED2044A3C60003C045 /* mobile_scanner_example.app */, 147 33CC10ED2044A3C60003C045 /* mobile_scanner_example.app */,
  148 + 331C80D5294CF71000263BE5 /* RunnerTests.xctest */,
126 ); 149 );
127 name = Products; 150 name = Products;
128 sourceTree = "<group>"; 151 sourceTree = "<group>";
@@ -162,10 +185,25 @@ @@ -162,10 +185,25 @@
162 path = Runner; 185 path = Runner;
163 sourceTree = "<group>"; 186 sourceTree = "<group>";
164 }; 187 };
165 - 64FFE901A03ED70F67D8DCD6 /* Frameworks */ = { 188 + 48C702343BA8440B9F0C4C3B /* Pods */ = {
166 isa = PBXGroup; 189 isa = PBXGroup;
167 children = ( 190 children = (
168 - 41950513928B2DA794C685E3 /* Pods_Runner.framework */, 191 + E1DC1D18B8C13B1C75763506 /* Pods-Runner.debug.xcconfig */,
  192 + CAEA4D71BCF9158847C4F6AC /* Pods-Runner.release.xcconfig */,
  193 + 8201964090D9F78BC65FF5D2 /* Pods-Runner.profile.xcconfig */,
  194 + AA0CC45DA8C7F4A77CA80A2E /* Pods-RunnerTests.debug.xcconfig */,
  195 + 23E216AF8BD40EB8E52863BE /* Pods-RunnerTests.release.xcconfig */,
  196 + 9AB9A83BADC7F755AA147D15 /* Pods-RunnerTests.profile.xcconfig */,
  197 + );
  198 + name = Pods;
  199 + path = Pods;
  200 + sourceTree = "<group>";
  201 + };
  202 + D73912EC22F37F3D000D13A0 /* Frameworks */ = {
  203 + isa = PBXGroup;
  204 + children = (
  205 + EEB40B96A6FFC92607527710 /* Pods_Runner.framework */,
  206 + 419C3BC9593F6DE903D740F0 /* Pods_RunnerTests.framework */,
169 ); 207 );
170 name = Frameworks; 208 name = Frameworks;
171 sourceTree = "<group>"; 209 sourceTree = "<group>";
@@ -173,17 +211,36 @@ @@ -173,17 +211,36 @@
173 /* End PBXGroup section */ 211 /* End PBXGroup section */
174 212
175 /* Begin PBXNativeTarget section */ 213 /* Begin PBXNativeTarget section */
  214 + 331C80D4294CF70F00263BE5 /* RunnerTests */ = {
  215 + isa = PBXNativeTarget;
  216 + buildConfigurationList = 331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */;
  217 + buildPhases = (
  218 + 26566874B4D5506B07E5CB72 /* [CP] Check Pods Manifest.lock */,
  219 + 331C80D1294CF70F00263BE5 /* Sources */,
  220 + 331C80D2294CF70F00263BE5 /* Frameworks */,
  221 + 331C80D3294CF70F00263BE5 /* Resources */,
  222 + );
  223 + buildRules = (
  224 + );
  225 + dependencies = (
  226 + 331C80DA294CF71000263BE5 /* PBXTargetDependency */,
  227 + );
  228 + name = RunnerTests;
  229 + productName = RunnerTests;
  230 + productReference = 331C80D5294CF71000263BE5 /* RunnerTests.xctest */;
  231 + productType = "com.apple.product-type.bundle.unit-test";
  232 + };
176 33CC10EC2044A3C60003C045 /* Runner */ = { 233 33CC10EC2044A3C60003C045 /* Runner */ = {
177 isa = PBXNativeTarget; 234 isa = PBXNativeTarget;
178 buildConfigurationList = 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */; 235 buildConfigurationList = 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */;
179 buildPhases = ( 236 buildPhases = (
180 - 11C0752B00246A027DB2D859 /* [CP] Check Pods Manifest.lock */, 237 + FF04EF1310CEA88A9000CF01 /* [CP] Check Pods Manifest.lock */,
181 33CC10E92044A3C60003C045 /* Sources */, 238 33CC10E92044A3C60003C045 /* Sources */,
182 33CC10EA2044A3C60003C045 /* Frameworks */, 239 33CC10EA2044A3C60003C045 /* Frameworks */,
183 33CC10EB2044A3C60003C045 /* Resources */, 240 33CC10EB2044A3C60003C045 /* Resources */,
184 33CC110E2044A8840003C045 /* Bundle Framework */, 241 33CC110E2044A8840003C045 /* Bundle Framework */,
185 3399D490228B24CF009A79C7 /* ShellScript */, 242 3399D490228B24CF009A79C7 /* ShellScript */,
186 - E6424ECF3C1308BAAF6E5E67 /* [CP] Embed Pods Frameworks */, 243 + 861067CDDCB0554984369B14 /* [CP] Embed Pods Frameworks */,
187 ); 244 );
188 buildRules = ( 245 buildRules = (
189 ); 246 );
@@ -202,9 +259,13 @@ @@ -202,9 +259,13 @@
202 isa = PBXProject; 259 isa = PBXProject;
203 attributes = { 260 attributes = {
204 LastSwiftUpdateCheck = 0920; 261 LastSwiftUpdateCheck = 0920;
205 - LastUpgradeCheck = 1300; 262 + LastUpgradeCheck = 1430;
206 ORGANIZATIONNAME = ""; 263 ORGANIZATIONNAME = "";
207 TargetAttributes = { 264 TargetAttributes = {
  265 + 331C80D4294CF70F00263BE5 = {
  266 + CreatedOnToolsVersion = 14.0;
  267 + TestTargetID = 33CC10EC2044A3C60003C045;
  268 + };
208 33CC10EC2044A3C60003C045 = { 269 33CC10EC2044A3C60003C045 = {
209 CreatedOnToolsVersion = 9.2; 270 CreatedOnToolsVersion = 9.2;
210 LastSwiftMigration = 1100; 271 LastSwiftMigration = 1100;
@@ -235,12 +296,20 @@ @@ -235,12 +296,20 @@
235 projectRoot = ""; 296 projectRoot = "";
236 targets = ( 297 targets = (
237 33CC10EC2044A3C60003C045 /* Runner */, 298 33CC10EC2044A3C60003C045 /* Runner */,
  299 + 331C80D4294CF70F00263BE5 /* RunnerTests */,
238 33CC111A2044C6BA0003C045 /* Flutter Assemble */, 300 33CC111A2044C6BA0003C045 /* Flutter Assemble */,
239 ); 301 );
240 }; 302 };
241 /* End PBXProject section */ 303 /* End PBXProject section */
242 304
243 /* Begin PBXResourcesBuildPhase section */ 305 /* Begin PBXResourcesBuildPhase section */
  306 + 331C80D3294CF70F00263BE5 /* Resources */ = {
  307 + isa = PBXResourcesBuildPhase;
  308 + buildActionMask = 2147483647;
  309 + files = (
  310 + );
  311 + runOnlyForDeploymentPostprocessing = 0;
  312 + };
244 33CC10EB2044A3C60003C045 /* Resources */ = { 313 33CC10EB2044A3C60003C045 /* Resources */ = {
245 isa = PBXResourcesBuildPhase; 314 isa = PBXResourcesBuildPhase;
246 buildActionMask = 2147483647; 315 buildActionMask = 2147483647;
@@ -253,7 +322,7 @@ @@ -253,7 +322,7 @@
253 /* End PBXResourcesBuildPhase section */ 322 /* End PBXResourcesBuildPhase section */
254 323
255 /* Begin PBXShellScriptBuildPhase section */ 324 /* Begin PBXShellScriptBuildPhase section */
256 - 11C0752B00246A027DB2D859 /* [CP] Check Pods Manifest.lock */ = { 325 + 26566874B4D5506B07E5CB72 /* [CP] Check Pods Manifest.lock */ = {
257 isa = PBXShellScriptBuildPhase; 326 isa = PBXShellScriptBuildPhase;
258 buildActionMask = 2147483647; 327 buildActionMask = 2147483647;
259 files = ( 328 files = (
@@ -268,7 +337,7 @@ @@ -268,7 +337,7 @@
268 outputFileListPaths = ( 337 outputFileListPaths = (
269 ); 338 );
270 outputPaths = ( 339 outputPaths = (
271 - "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", 340 + "$(DERIVED_FILE_DIR)/Pods-RunnerTests-checkManifestLockResult.txt",
272 ); 341 );
273 runOnlyForDeploymentPostprocessing = 0; 342 runOnlyForDeploymentPostprocessing = 0;
274 shellPath = /bin/sh; 343 shellPath = /bin/sh;
@@ -313,7 +382,7 @@ @@ -313,7 +382,7 @@
313 shellPath = /bin/sh; 382 shellPath = /bin/sh;
314 shellScript = "\"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh && touch Flutter/ephemeral/tripwire"; 383 shellScript = "\"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh && touch Flutter/ephemeral/tripwire";
315 }; 384 };
316 - E6424ECF3C1308BAAF6E5E67 /* [CP] Embed Pods Frameworks */ = { 385 + 861067CDDCB0554984369B14 /* [CP] Embed Pods Frameworks */ = {
317 isa = PBXShellScriptBuildPhase; 386 isa = PBXShellScriptBuildPhase;
318 buildActionMask = 2147483647; 387 buildActionMask = 2147483647;
319 files = ( 388 files = (
@@ -330,9 +399,39 @@ @@ -330,9 +399,39 @@
330 shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; 399 shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n";
331 showEnvVarsInLog = 0; 400 showEnvVarsInLog = 0;
332 }; 401 };
  402 + FF04EF1310CEA88A9000CF01 /* [CP] Check Pods Manifest.lock */ = {
  403 + isa = PBXShellScriptBuildPhase;
  404 + buildActionMask = 2147483647;
  405 + files = (
  406 + );
  407 + inputFileListPaths = (
  408 + );
  409 + inputPaths = (
  410 + "${PODS_PODFILE_DIR_PATH}/Podfile.lock",
  411 + "${PODS_ROOT}/Manifest.lock",
  412 + );
  413 + name = "[CP] Check Pods Manifest.lock";
  414 + outputFileListPaths = (
  415 + );
  416 + outputPaths = (
  417 + "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt",
  418 + );
  419 + runOnlyForDeploymentPostprocessing = 0;
  420 + shellPath = /bin/sh;
  421 + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
  422 + showEnvVarsInLog = 0;
  423 + };
333 /* End PBXShellScriptBuildPhase section */ 424 /* End PBXShellScriptBuildPhase section */
334 425
335 /* Begin PBXSourcesBuildPhase section */ 426 /* Begin PBXSourcesBuildPhase section */
  427 + 331C80D1294CF70F00263BE5 /* Sources */ = {
  428 + isa = PBXSourcesBuildPhase;
  429 + buildActionMask = 2147483647;
  430 + files = (
  431 + 331C80D8294CF71000263BE5 /* RunnerTests.swift in Sources */,
  432 + );
  433 + runOnlyForDeploymentPostprocessing = 0;
  434 + };
336 33CC10E92044A3C60003C045 /* Sources */ = { 435 33CC10E92044A3C60003C045 /* Sources */ = {
337 isa = PBXSourcesBuildPhase; 436 isa = PBXSourcesBuildPhase;
338 buildActionMask = 2147483647; 437 buildActionMask = 2147483647;
@@ -346,6 +445,11 @@ @@ -346,6 +445,11 @@
346 /* End PBXSourcesBuildPhase section */ 445 /* End PBXSourcesBuildPhase section */
347 446
348 /* Begin PBXTargetDependency section */ 447 /* Begin PBXTargetDependency section */
  448 + 331C80DA294CF71000263BE5 /* PBXTargetDependency */ = {
  449 + isa = PBXTargetDependency;
  450 + target = 33CC10EC2044A3C60003C045 /* Runner */;
  451 + targetProxy = 331C80D9294CF71000263BE5 /* PBXContainerItemProxy */;
  452 + };
349 33CC11202044C79F0003C045 /* PBXTargetDependency */ = { 453 33CC11202044C79F0003C045 /* PBXTargetDependency */ = {
350 isa = PBXTargetDependency; 454 isa = PBXTargetDependency;
351 target = 33CC111A2044C6BA0003C045 /* Flutter Assemble */; 455 target = 33CC111A2044C6BA0003C045 /* Flutter Assemble */;
@@ -366,6 +470,51 @@ @@ -366,6 +470,51 @@
366 /* End PBXVariantGroup section */ 470 /* End PBXVariantGroup section */
367 471
368 /* Begin XCBuildConfiguration section */ 472 /* Begin XCBuildConfiguration section */
  473 + 331C80DB294CF71000263BE5 /* Debug */ = {
  474 + isa = XCBuildConfiguration;
  475 + baseConfigurationReference = AA0CC45DA8C7F4A77CA80A2E /* Pods-RunnerTests.debug.xcconfig */;
  476 + buildSettings = {
  477 + BUNDLE_LOADER = "$(TEST_HOST)";
  478 + CURRENT_PROJECT_VERSION = 1;
  479 + GENERATE_INFOPLIST_FILE = YES;
  480 + MARKETING_VERSION = 1.0;
  481 + PRODUCT_BUNDLE_IDENTIFIER = dev.steenbakker.mobileScannerExample.RunnerTests;
  482 + PRODUCT_NAME = "$(TARGET_NAME)";
  483 + SWIFT_VERSION = 5.0;
  484 + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/mobile_scanner_example.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/mobile_scanner_example";
  485 + };
  486 + name = Debug;
  487 + };
  488 + 331C80DC294CF71000263BE5 /* Release */ = {
  489 + isa = XCBuildConfiguration;
  490 + baseConfigurationReference = 23E216AF8BD40EB8E52863BE /* Pods-RunnerTests.release.xcconfig */;
  491 + buildSettings = {
  492 + BUNDLE_LOADER = "$(TEST_HOST)";
  493 + CURRENT_PROJECT_VERSION = 1;
  494 + GENERATE_INFOPLIST_FILE = YES;
  495 + MARKETING_VERSION = 1.0;
  496 + PRODUCT_BUNDLE_IDENTIFIER = dev.steenbakker.mobileScannerExample.RunnerTests;
  497 + PRODUCT_NAME = "$(TARGET_NAME)";
  498 + SWIFT_VERSION = 5.0;
  499 + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/mobile_scanner_example.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/mobile_scanner_example";
  500 + };
  501 + name = Release;
  502 + };
  503 + 331C80DD294CF71000263BE5 /* Profile */ = {
  504 + isa = XCBuildConfiguration;
  505 + baseConfigurationReference = 9AB9A83BADC7F755AA147D15 /* Pods-RunnerTests.profile.xcconfig */;
  506 + buildSettings = {
  507 + BUNDLE_LOADER = "$(TEST_HOST)";
  508 + CURRENT_PROJECT_VERSION = 1;
  509 + GENERATE_INFOPLIST_FILE = YES;
  510 + MARKETING_VERSION = 1.0;
  511 + PRODUCT_BUNDLE_IDENTIFIER = dev.steenbakker.mobileScannerExample.RunnerTests;
  512 + PRODUCT_NAME = "$(TARGET_NAME)";
  513 + SWIFT_VERSION = 5.0;
  514 + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/mobile_scanner_example.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/mobile_scanner_example";
  515 + };
  516 + name = Profile;
  517 + };
369 338D0CE9231458BD00FA5F75 /* Profile */ = { 518 338D0CE9231458BD00FA5F75 /* Profile */ = {
370 isa = XCBuildConfiguration; 519 isa = XCBuildConfiguration;
371 baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; 520 baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
@@ -599,6 +748,16 @@ @@ -599,6 +748,16 @@
599 /* End XCBuildConfiguration section */ 748 /* End XCBuildConfiguration section */
600 749
601 /* Begin XCConfigurationList section */ 750 /* Begin XCConfigurationList section */
  751 + 331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = {
  752 + isa = XCConfigurationList;
  753 + buildConfigurations = (
  754 + 331C80DB294CF71000263BE5 /* Debug */,
  755 + 331C80DC294CF71000263BE5 /* Release */,
  756 + 331C80DD294CF71000263BE5 /* Profile */,
  757 + );
  758 + defaultConfigurationIsVisible = 0;
  759 + defaultConfigurationName = Release;
  760 + };
602 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */ = { 761 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */ = {
603 isa = XCConfigurationList; 762 isa = XCConfigurationList;
604 buildConfigurations = ( 763 buildConfigurations = (
1 <?xml version="1.0" encoding="UTF-8"?> 1 <?xml version="1.0" encoding="UTF-8"?>
2 <Scheme 2 <Scheme
3 - LastUpgradeVersion = "1300" 3 + LastUpgradeVersion = "1430"
4 version = "1.3"> 4 version = "1.3">
5 <BuildAction 5 <BuildAction
6 parallelizeBuildables = "YES" 6 parallelizeBuildables = "YES"
@@ -37,6 +37,17 @@ @@ -37,6 +37,17 @@
37 </BuildableReference> 37 </BuildableReference>
38 </MacroExpansion> 38 </MacroExpansion>
39 <Testables> 39 <Testables>
  40 + <TestableReference
  41 + skipped = "NO"
  42 + parallelizable = "YES">
  43 + <BuildableReference
  44 + BuildableIdentifier = "primary"
  45 + BlueprintIdentifier = "331C80D4294CF70F00263BE5"
  46 + BuildableName = "RunnerTests.xctest"
  47 + BlueprintName = "RunnerTests"
  48 + ReferencedContainer = "container:Runner.xcodeproj">
  49 + </BuildableReference>
  50 + </TestableReference>
40 </Testables> 51 </Testables>
41 </TestAction> 52 </TestAction>
42 <LaunchAction 53 <LaunchAction
@@ -11,4 +11,4 @@ PRODUCT_NAME = mobile_scanner_example @@ -11,4 +11,4 @@ PRODUCT_NAME = mobile_scanner_example
11 PRODUCT_BUNDLE_IDENTIFIER = dev.steenbakker.mobileScannerExample 11 PRODUCT_BUNDLE_IDENTIFIER = dev.steenbakker.mobileScannerExample
12 12
13 // The copyright displayed in application information 13 // The copyright displayed in application information
14 -PRODUCT_COPYRIGHT = Copyright © 2022 dev.steenbakker. All rights reserved. 14 +PRODUCT_COPYRIGHT = Copyright © 2023 dev.steenbakker. All rights reserved.
@@ -3,7 +3,7 @@ import FlutterMacOS @@ -3,7 +3,7 @@ import FlutterMacOS
3 3
4 class MainFlutterWindow: NSWindow { 4 class MainFlutterWindow: NSWindow {
5 override func awakeFromNib() { 5 override func awakeFromNib() {
6 - let flutterViewController = FlutterViewController.init() 6 + let flutterViewController = FlutterViewController()
7 let windowFrame = self.frame 7 let windowFrame = self.frame
8 self.contentViewController = flutterViewController 8 self.contentViewController = flutterViewController
9 self.setFrame(windowFrame, display: true) 9 self.setFrame(windowFrame, display: true)
  1 +import FlutterMacOS
  2 +import Cocoa
  3 +import XCTest
  4 +
  5 +@testable import mobile_scanner
  6 +
  7 +// This demonstrates a simple unit test of the Swift portion of this plugin's implementation.
  8 +//
  9 +// See https://developer.apple.com/documentation/xctest for more information about using XCTest.
  10 +
  11 +class RunnerTests: XCTestCase {
  12 +
  13 + // TODO: this test was left as-is from the template, but it obviuosly fails for now.
  14 + // Add new tests later.
  15 + /*
  16 + func testGetPlatformVersion() {
  17 + let plugin = MobileScannerPlugin()
  18 +
  19 + let call = FlutterMethodCall(methodName: "getPlatformVersion", arguments: [])
  20 +
  21 + let resultExpectation = expectation(description: "result block must be called.")
  22 + plugin.handle(call) { result in
  23 + XCTAssertEqual(result as! String,
  24 + "macOS " + ProcessInfo.processInfo.operatingSystemVersionString)
  25 + resultExpectation.fulfill()
  26 + }
  27 + waitForExpectations(timeout: 1)
  28 + }*/
  29 +
  30 +}
1 name: mobile_scanner_example 1 name: mobile_scanner_example
2 description: Demonstrates how to use the mobile_scanner plugin. 2 description: Demonstrates how to use the mobile_scanner plugin.
  3 +# The following line prevents the package from being accidentally published to
  4 +# pub.dev using `flutter pub publish`. This is preferred for private packages.
3 publish_to: 'none' # Remove this line if you wish to publish to pub.dev 5 publish_to: 'none' # Remove this line if you wish to publish to pub.dev
  6 +version: 0.0.1
4 7
5 environment: 8 environment:
6 - sdk: ">=2.12.0 <4.0.0" 9 + sdk: '>=2.18.0 <4.0.0'
7 10
  11 +# Dependencies specify other packages that your package needs in order to work.
  12 +# To automatically upgrade your package dependencies to the latest versions
  13 +# consider running `flutter pub upgrade --major-versions`. Alternatively,
  14 +# dependencies can be manually updated by changing the version numbers below to
  15 +# the latest version available on pub.dev. To see which dependencies have newer
  16 +# versions available, run `flutter pub outdated`.
8 dependencies: 17 dependencies:
9 flutter: 18 flutter:
10 sdk: flutter 19 sdk: flutter
11 - image_picker: ^1.0.0  
12 20
  21 + image_picker: ^1.0.4
13 mobile_scanner: 22 mobile_scanner:
  23 + # When depending on this package from a real application you should use:
  24 + # mobile_scanner: ^x.y.z
  25 + # See https://dart.dev/tools/pub/dependencies#version-constraints
  26 + # The example app is bundled with the plugin so we use a path dependency on
  27 + # the parent directory to use the current plugin's version.
14 path: ../ 28 path: ../
15 29
16 dev_dependencies: 30 dev_dependencies:
17 flutter_test: 31 flutter_test:
18 sdk: flutter 32 sdk: flutter
19 - lint: ^2.0.1 33 + integration_test:
  34 + sdk: flutter
  35 + lint: ^2.1.2
20 36
21 flutter: 37 flutter:
22 uses-material-design: true 38 uses-material-design: true
1 -// This is a basic Flutter widget test.  
2 -//  
3 -// To perform an interaction with a widget in your test, use the WidgetTester  
4 -// utility that Flutter provides. For example, you can send tap and scroll  
5 -// gestures. You can also use WidgetTester to find child widgets in the widget  
6 -// tree, read text, and verify that the values of widget properties are correct.  
7 -  
8 -import 'package:flutter_test/flutter_test.dart';  
9 -  
10 -void main() {  
11 - testWidgets('Verify Platform version', (WidgetTester tester) async {  
12 - // Build our app and trigger a frame.  
13 - });  
14 -}  
@@ -34,7 +34,7 @@ @@ -34,7 +34,7 @@
34 34
35 <script> 35 <script>
36 // The value below is injected by flutter build, do not touch. 36 // The value below is injected by flutter build, do not touch.
37 - var serviceWorkerVersion = null; 37 + const serviceWorkerVersion = null;
38 </script> 38 </script>
39 <!-- This script adds the flutter initialization JS code --> 39 <!-- This script adds the flutter initialization JS code -->
40 <script src="flutter.js" defer></script> 40 <script src="flutter.js" defer></script>
@@ -5,12 +5,10 @@ @@ -5,12 +5,10 @@
5 // Created by Julian Steenbakker on 24/08/2022. 5 // Created by Julian Steenbakker on 24/08/2022.
6 // 6 //
7 7
  8 +import Flutter
8 import Foundation 9 import Foundation
9 10
10 public class BarcodeHandler: NSObject, FlutterStreamHandler { 11 public class BarcodeHandler: NSObject, FlutterStreamHandler {
11 -  
12 - var event: [String: Any?] = [:]  
13 -  
14 private var eventSink: FlutterEventSink? 12 private var eventSink: FlutterEventSink?
15 private let eventChannel: FlutterEventChannel 13 private let eventChannel: FlutterEventChannel
16 14
@@ -22,8 +20,9 @@ public class BarcodeHandler: NSObject, FlutterStreamHandler { @@ -22,8 +20,9 @@ public class BarcodeHandler: NSObject, FlutterStreamHandler {
22 } 20 }
23 21
24 func publishEvent(_ event: [String: Any?]) { 22 func publishEvent(_ event: [String: Any?]) {
25 - self.event = event  
26 - eventSink?(event) 23 + DispatchQueue.main.async {
  24 + self.eventSink?(event)
  25 + }
27 } 26 }
28 27
29 public func onListen(withArguments arguments: Any?, 28 public func onListen(withArguments arguments: Any?,
1 // 1 //
2 -// SwiftMobileScanner.swift 2 +// MobileScanner.swift
3 // mobile_scanner 3 // mobile_scanner
4 // 4 //
5 // Created by Julian Steenbakker on 15/02/2022. 5 // Created by Julian Steenbakker on 15/02/2022.
6 // 6 //
7 7
8 import Foundation 8 import Foundation
9 - 9 +import Flutter
10 import AVFoundation 10 import AVFoundation
11 import MLKitVision 11 import MLKitVision
12 import MLKitBarcodeScanning 12 import MLKitBarcodeScanning
@@ -55,6 +55,12 @@ public class MobileScanner: NSObject, AVCaptureVideoDataOutputSampleBufferDelega @@ -55,6 +55,12 @@ public class MobileScanner: NSObject, AVCaptureVideoDataOutputSampleBufferDelega
55 55
56 var standardZoomFactor: CGFloat = 1 56 var standardZoomFactor: CGFloat = 1
57 57
  58 + private var nextScanTime = 0.0
  59 +
  60 + private var imagesCurrentlyBeingProcessed = false
  61 +
  62 + public var timeoutSeconds: Double = 0
  63 +
58 init(registry: FlutterTextureRegistry?, mobileScannerCallback: @escaping MobileScannerCallback, torchModeChangeCallback: @escaping TorchModeChangeCallback, zoomScaleChangeCallback: @escaping ZoomScaleChangeCallback) { 64 init(registry: FlutterTextureRegistry?, mobileScannerCallback: @escaping MobileScannerCallback, torchModeChangeCallback: @escaping TorchModeChangeCallback, zoomScaleChangeCallback: @escaping ZoomScaleChangeCallback) {
59 self.registry = registry 65 self.registry = registry
60 self.mobileScannerCallback = mobileScannerCallback 66 self.mobileScannerCallback = mobileScannerCallback
@@ -89,8 +95,15 @@ public class MobileScanner: NSObject, AVCaptureVideoDataOutputSampleBufferDelega @@ -89,8 +95,15 @@ public class MobileScanner: NSObject, AVCaptureVideoDataOutputSampleBufferDelega
89 } 95 }
90 latestBuffer = imageBuffer 96 latestBuffer = imageBuffer
91 registry?.textureFrameAvailable(textureId) 97 registry?.textureFrameAvailable(textureId)
92 - if ((detectionSpeed == DetectionSpeed.normal || detectionSpeed == DetectionSpeed.noDuplicates) && i > 10 || detectionSpeed == DetectionSpeed.unrestricted) {  
93 - i = 0 98 +
  99 + let currentTime = Date().timeIntervalSince1970
  100 + let eligibleForScan = currentTime > nextScanTime && !imagesCurrentlyBeingProcessed
  101 +
  102 + if ((detectionSpeed == DetectionSpeed.normal || detectionSpeed == DetectionSpeed.noDuplicates) && eligibleForScan || detectionSpeed == DetectionSpeed.unrestricted) {
  103 +
  104 + nextScanTime = currentTime + timeoutSeconds
  105 + imagesCurrentlyBeingProcessed = true
  106 +
94 let ciImage = latestBuffer.image 107 let ciImage = latestBuffer.image
95 108
96 let image = VisionImage(image: ciImage) 109 let image = VisionImage(image: ciImage)
@@ -101,31 +114,33 @@ public class MobileScanner: NSObject, AVCaptureVideoDataOutputSampleBufferDelega @@ -101,31 +114,33 @@ public class MobileScanner: NSObject, AVCaptureVideoDataOutputSampleBufferDelega
101 ) 114 )
102 115
103 scanner.process(image) { [self] barcodes, error in 116 scanner.process(image) { [self] barcodes, error in
  117 + imagesCurrentlyBeingProcessed = false
  118 +
104 if (detectionSpeed == DetectionSpeed.noDuplicates) { 119 if (detectionSpeed == DetectionSpeed.noDuplicates) {
105 - let newScannedBarcodes = barcodes?.map { barcode in 120 + let newScannedBarcodes = barcodes?.compactMap({ barcode in
106 return barcode.rawValue 121 return barcode.rawValue
107 - } 122 + }).sorted()
  123 +
108 if (error == nil && barcodesString != nil && newScannedBarcodes != nil && barcodesString!.elementsEqual(newScannedBarcodes!)) { 124 if (error == nil && barcodesString != nil && newScannedBarcodes != nil && barcodesString!.elementsEqual(newScannedBarcodes!)) {
109 return 125 return
110 - } else { 126 + } else if (newScannedBarcodes?.isEmpty == false) {
111 barcodesString = newScannedBarcodes 127 barcodesString = newScannedBarcodes
112 } 128 }
113 } 129 }
114 130
115 mobileScannerCallback(barcodes, error, ciImage) 131 mobileScannerCallback(barcodes, error, ciImage)
116 } 132 }
117 - } else {  
118 - i+=1  
119 } 133 }
120 } 134 }
121 135
122 /// Start scanning for barcodes 136 /// Start scanning for barcodes
123 - func start(barcodeScannerOptions: BarcodeScannerOptions?, returnImage: Bool, cameraPosition: AVCaptureDevice.Position, torch: AVCaptureDevice.TorchMode, detectionSpeed: DetectionSpeed, completion: @escaping (MobileScannerStartParameters) -> ()) throws { 137 + func start(barcodeScannerOptions: BarcodeScannerOptions?, returnImage: Bool, cameraPosition: AVCaptureDevice.Position, torch: Bool, detectionSpeed: DetectionSpeed, completion: @escaping (MobileScannerStartParameters) -> ()) throws {
124 self.detectionSpeed = detectionSpeed 138 self.detectionSpeed = detectionSpeed
125 if (device != nil) { 139 if (device != nil) {
126 throw MobileScannerError.alreadyStarted 140 throw MobileScannerError.alreadyStarted
127 } 141 }
128 142
  143 + barcodesString = nil
129 scanner = barcodeScannerOptions != nil ? BarcodeScanner.barcodeScanner(options: barcodeScannerOptions!) : BarcodeScanner.barcodeScanner() 144 scanner = barcodeScannerOptions != nil ? BarcodeScanner.barcodeScanner(options: barcodeScannerOptions!) : BarcodeScanner.barcodeScanner()
130 captureSession = AVCaptureSession() 145 captureSession = AVCaptureSession()
131 textureId = registry?.register(self) 146 textureId = registry?.register(self)
@@ -178,7 +193,7 @@ public class MobileScanner: NSObject, AVCaptureVideoDataOutputSampleBufferDelega @@ -178,7 +193,7 @@ public class MobileScanner: NSObject, AVCaptureVideoDataOutputSampleBufferDelega
178 throw MobileScannerError.cameraError(error) 193 throw MobileScannerError.cameraError(error)
179 } 194 }
180 195
181 - captureSession.sessionPreset = AVCaptureSession.Preset.photo; 196 + captureSession.sessionPreset = AVCaptureSession.Preset.photo
182 // Add video output. 197 // Add video output.
183 let videoOutput = AVCaptureVideoDataOutput() 198 let videoOutput = AVCaptureVideoDataOutput()
184 199
@@ -200,32 +215,43 @@ public class MobileScanner: NSObject, AVCaptureVideoDataOutputSampleBufferDelega @@ -200,32 +215,43 @@ public class MobileScanner: NSObject, AVCaptureVideoDataOutputSampleBufferDelega
200 215
201 backgroundQueue.async { 216 backgroundQueue.async {
202 self.captureSession.startRunning() 217 self.captureSession.startRunning()
203 - // Enable the torch if parameter is set and torch is available  
204 - // torch should be set after 'startRunning' is called 218 +
  219 + // Turn on the flashlight if requested,
  220 + // but after the capture session started.
  221 + if (torch) {
205 do { 222 do {
206 - try self.toggleTorch(torch) 223 + try self.toggleTorch(.on)
207 } catch { 224 } catch {
208 - print("Failed to set initial torch state.") 225 + // If the torch does not turn on,
  226 + // continue with the capture session anyway.
  227 + }
209 } 228 }
210 229
211 do { 230 do {
212 try self.resetScale() 231 try self.resetScale()
213 } catch { 232 } catch {
214 - print("Failed to reset zoom scale") 233 + // If the zoom scale could not be reset,
  234 + // continue with the capture session anyway.
215 } 235 }
216 236
217 - let dimensions = CMVideoFormatDescriptionGetDimensions(self.device.activeFormat.formatDescription) 237 + if let device = self.device {
  238 + let dimensions = CMVideoFormatDescriptionGetDimensions(
  239 + device.activeFormat.formatDescription)
  240 + let hasTorch = device.hasTorch
218 241
219 - DispatchQueue.main.async {  
220 completion( 242 completion(
221 MobileScannerStartParameters( 243 MobileScannerStartParameters(
222 width: Double(dimensions.height), 244 width: Double(dimensions.height),
223 height: Double(dimensions.width), 245 height: Double(dimensions.width),
224 - hasTorch: self.device.hasTorch,  
225 - textureId: self.textureId 246 + hasTorch: hasTorch,
  247 + textureId: self.textureId ?? 0
226 ) 248 )
227 ) 249 )
  250 +
  251 + return
228 } 252 }
  253 +
  254 + completion(MobileScannerStartParameters())
229 } 255 }
230 } 256 }
231 257
@@ -251,19 +277,16 @@ public class MobileScanner: NSObject, AVCaptureVideoDataOutputSampleBufferDelega @@ -251,19 +277,16 @@ public class MobileScanner: NSObject, AVCaptureVideoDataOutputSampleBufferDelega
251 device = nil 277 device = nil
252 } 278 }
253 279
254 - /// Toggle the flashlight between on and off 280 + /// Toggle the flashlight between on and off.
255 func toggleTorch(_ torch: AVCaptureDevice.TorchMode) throws { 281 func toggleTorch(_ torch: AVCaptureDevice.TorchMode) throws {
256 - if (device == nil) {  
257 - throw MobileScannerError.torchWhenStopped 282 + if (device == nil || !device.hasTorch || !device.isTorchAvailable) {
  283 + return
258 } 284 }
259 - if (device.hasTorch && device.isTorchAvailable) {  
260 - do { 285 +
  286 + if (device.torchMode != torch) {
261 try device.lockForConfiguration() 287 try device.lockForConfiguration()
262 device.torchMode = torch 288 device.torchMode = torch
263 device.unlockForConfiguration() 289 device.unlockForConfiguration()
264 - } catch {  
265 - throw MobileScannerError.torchError(error)  
266 - }  
267 } 290 }
268 } 291 }
269 292
@@ -271,7 +294,7 @@ public class MobileScanner: NSObject, AVCaptureVideoDataOutputSampleBufferDelega @@ -271,7 +294,7 @@ public class MobileScanner: NSObject, AVCaptureVideoDataOutputSampleBufferDelega
271 public override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) { 294 public override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
272 switch keyPath { 295 switch keyPath {
273 case "torchMode": 296 case "torchMode":
274 - // off = 0; on = 1; auto = 2; 297 + // off = 0; on = 1; auto = 2
275 let state = change?[.newKey] as? Int 298 let state = change?[.newKey] as? Int
276 torchModeChangeCallback(state) 299 torchModeChangeCallback(state)
277 case "videoZoomFactor": 300 case "videoZoomFactor":
@@ -286,12 +309,12 @@ public class MobileScanner: NSObject, AVCaptureVideoDataOutputSampleBufferDelega @@ -286,12 +309,12 @@ public class MobileScanner: NSObject, AVCaptureVideoDataOutputSampleBufferDelega
286 /// Set the zoom factor of the camera 309 /// Set the zoom factor of the camera
287 func setScale(_ scale: CGFloat) throws { 310 func setScale(_ scale: CGFloat) throws {
288 if (device == nil) { 311 if (device == nil) {
289 - throw MobileScannerError.torchWhenStopped 312 + throw MobileScannerError.zoomWhenStopped
290 } 313 }
291 314
292 do { 315 do {
293 try device.lockForConfiguration() 316 try device.lockForConfiguration()
294 - var maxZoomFactor = device.activeFormat.videoMaxZoomFactor 317 + let maxZoomFactor = device.activeFormat.videoMaxZoomFactor
295 318
296 var actualScale = (scale * 4) + 1 319 var actualScale = (scale * 4) + 1
297 320
@@ -325,7 +348,6 @@ public class MobileScanner: NSObject, AVCaptureVideoDataOutputSampleBufferDelega @@ -325,7 +348,6 @@ public class MobileScanner: NSObject, AVCaptureVideoDataOutputSampleBufferDelega
325 } 348 }
326 } 349 }
327 350
328 -  
329 /// Analyze a single image 351 /// Analyze a single image
330 func analyzeImage(image: UIImage, position: AVCaptureDevice.Position, callback: @escaping BarcodeScanningCallback) { 352 func analyzeImage(image: UIImage, position: AVCaptureDevice.Position, callback: @escaping BarcodeScanningCallback) {
331 let image = VisionImage(image: image) 353 let image = VisionImage(image: image)
@@ -338,22 +360,18 @@ public class MobileScanner: NSObject, AVCaptureVideoDataOutputSampleBufferDelega @@ -338,22 +360,18 @@ public class MobileScanner: NSObject, AVCaptureVideoDataOutputSampleBufferDelega
338 scanner.process(image, completion: callback) 360 scanner.process(image, completion: callback)
339 } 361 }
340 362
341 - var i = 0  
342 -  
343 var barcodesString: Array<String?>? 363 var barcodesString: Array<String?>?
344 364
345 -  
346 -  
347 -// /// Convert image buffer to jpeg  
348 -// private func ciImageToJpeg(ciImage: CIImage) -> Data {  
349 -//  
350 -// // let ciImage = CIImage(cvPixelBuffer: latestBuffer)  
351 -// let context:CIContext = CIContext.init(options: nil)  
352 -// let cgImage:CGImage = context.createCGImage(ciImage, from: ciImage.extent)!  
353 -// let uiImage:UIImage = UIImage(cgImage: cgImage, scale: 1, orientation: UIImage.Orientation.up)  
354 -//  
355 -// return uiImage.jpegData(compressionQuality: 0.8)!;  
356 -// } 365 + // /// Convert image buffer to jpeg
  366 + // private func ciImageToJpeg(ciImage: CIImage) -> Data {
  367 + //
  368 + // // let ciImage = CIImage(cvPixelBuffer: latestBuffer)
  369 + // let context:CIContext = CIContext.init(options: nil)
  370 + // let cgImage:CGImage = context.createCGImage(ciImage, from: ciImage.extent)!
  371 + // let uiImage:UIImage = UIImage(cgImage: cgImage, scale: 1, orientation: UIImage.Orientation.up)
  372 + //
  373 + // return uiImage.jpegData(compressionQuality: 0.8)!
  374 + // }
357 375
358 /// Rotates images accordingly 376 /// Rotates images accordingly
359 func imageOrientation( 377 func imageOrientation(
@@ -10,9 +10,7 @@ enum MobileScannerError: Error { @@ -10,9 +10,7 @@ enum MobileScannerError: Error {
10 case noCamera 10 case noCamera
11 case alreadyStarted 11 case alreadyStarted
12 case alreadyStopped 12 case alreadyStopped
13 - case torchError(_ error: Error)  
14 case cameraError(_ error: Error) 13 case cameraError(_ error: Error)
15 - case torchWhenStopped  
16 case zoomWhenStopped 14 case zoomWhenStopped
17 case zoomError(_ error: Error) 15 case zoomError(_ error: Error)
18 case analyzerError(_ error: Error) 16 case analyzerError(_ error: Error)
1 -#import <Flutter/Flutter.h>  
2 -  
3 -@interface MobileScannerPlugin : NSObject<FlutterPlugin>  
4 -@end  
1 -#import "MobileScannerPlugin.h"  
2 -#if __has_include(<mobile_scanner/mobile_scanner-Swift.h>)  
3 -#import <mobile_scanner/mobile_scanner-Swift.h>  
4 -#else  
5 -// Support project import fallback if the generated compatibility header  
6 -// is not copied when this plugin is created as a library.  
7 -// https://forums.swift.org/t/swift-static-libraries-dont-copy-generated-objective-c-header/19816  
8 -#import "mobile_scanner-Swift.h"  
9 -#endif  
10 -  
11 -@implementation MobileScannerPlugin  
12 -+ (void)registerWithRegistrar:(NSObject<FlutterPluginRegistrar>*)registrar {  
13 - [SwiftMobileScannerPlugin registerWithRegistrar:registrar];  
14 -}  
15 -@end  
@@ -4,7 +4,7 @@ import MLKitBarcodeScanning @@ -4,7 +4,7 @@ import MLKitBarcodeScanning
4 import AVFoundation 4 import AVFoundation
5 import UIKit 5 import UIKit
6 6
7 -public class SwiftMobileScannerPlugin: NSObject, FlutterPlugin { 7 +public class MobileScannerPlugin: NSObject, FlutterPlugin {
8 8
9 /// The mobile scanner object that handles all logic 9 /// The mobile scanner object that handles all logic
10 private let mobileScanner: MobileScanner 10 private let mobileScanner: MobileScanner
@@ -12,10 +12,11 @@ public class SwiftMobileScannerPlugin: NSObject, FlutterPlugin { @@ -12,10 +12,11 @@ public class SwiftMobileScannerPlugin: NSObject, FlutterPlugin {
12 /// The handler sends all information via an event channel back to Flutter 12 /// The handler sends all information via an event channel back to Flutter
13 private let barcodeHandler: BarcodeHandler 13 private let barcodeHandler: BarcodeHandler
14 14
  15 + /// The points for the scan window.
15 static var scanWindow: [CGFloat]? 16 static var scanWindow: [CGFloat]?
16 17
17 private static func isBarcodeInScanWindow(barcode: Barcode, imageSize: CGSize) -> Bool { 18 private static func isBarcodeInScanWindow(barcode: Barcode, imageSize: CGSize) -> Bool {
18 - let scanwindow = SwiftMobileScannerPlugin.scanWindow! 19 + let scanwindow = MobileScannerPlugin.scanWindow!
19 let barcodeminX = barcode.cornerPoints![0].cgPointValue.x 20 let barcodeminX = barcode.cornerPoints![0].cgPointValue.x
20 let barcodeminY = barcode.cornerPoints![1].cgPointValue.y 21 let barcodeminY = barcode.cornerPoints![1].cgPointValue.y
21 22
@@ -23,7 +24,6 @@ public class SwiftMobileScannerPlugin: NSObject, FlutterPlugin { @@ -23,7 +24,6 @@ public class SwiftMobileScannerPlugin: NSObject, FlutterPlugin {
23 let barcodeheight = barcode.cornerPoints![3].cgPointValue.y - barcodeminY 24 let barcodeheight = barcode.cornerPoints![3].cgPointValue.y - barcodeminY
24 let barcodeBox = CGRect(x: barcodeminX, y: barcodeminY, width: barcodewidth, height: barcodeheight) 25 let barcodeBox = CGRect(x: barcodeminX, y: barcodeminY, width: barcodewidth, height: barcodeheight)
25 26
26 -  
27 let minX = scanwindow[0] * imageSize.width 27 let minX = scanwindow[0] * imageSize.width
28 let minY = scanwindow[1] * imageSize.height 28 let minY = scanwindow[1] * imageSize.height
29 29
@@ -39,8 +39,8 @@ public class SwiftMobileScannerPlugin: NSObject, FlutterPlugin { @@ -39,8 +39,8 @@ public class SwiftMobileScannerPlugin: NSObject, FlutterPlugin {
39 self.mobileScanner = MobileScanner(registry: registry, mobileScannerCallback: { barcodes, error, image in 39 self.mobileScanner = MobileScanner(registry: registry, mobileScannerCallback: { barcodes, error, image in
40 if barcodes != nil { 40 if barcodes != nil {
41 let barcodesMap: [Any?] = barcodes!.compactMap { barcode in 41 let barcodesMap: [Any?] = barcodes!.compactMap { barcode in
42 - if (SwiftMobileScannerPlugin.scanWindow != nil) {  
43 - if (SwiftMobileScannerPlugin.isBarcodeInScanWindow(barcode: barcode, imageSize: image.size)) { 42 + if (MobileScannerPlugin.scanWindow != nil) {
  43 + if (MobileScannerPlugin.isBarcodeInScanWindow(barcode: barcode, imageSize: image.size)) {
44 return barcode.data 44 return barcode.data
45 } else { 45 } else {
46 return nil 46 return nil
@@ -65,10 +65,9 @@ public class SwiftMobileScannerPlugin: NSObject, FlutterPlugin { @@ -65,10 +65,9 @@ public class SwiftMobileScannerPlugin: NSObject, FlutterPlugin {
65 } 65 }
66 66
67 public static func register(with registrar: FlutterPluginRegistrar) { 67 public static func register(with registrar: FlutterPluginRegistrar) {
68 - let instance = SwiftMobileScannerPlugin(barcodeHandler: BarcodeHandler(registrar: registrar), registry: registrar.textures())  
69 - let methodChannel = FlutterMethodChannel(name:  
70 - "dev.steenbakker.mobile_scanner/scanner/method", binaryMessenger: registrar.messenger())  
71 - registrar.addMethodCallDelegate(instance, channel: methodChannel) 68 + let channel = FlutterMethodChannel(name: "dev.steenbakker.mobile_scanner/scanner/method", binaryMessenger: registrar.messenger())
  69 + let instance = MobileScannerPlugin(barcodeHandler: BarcodeHandler(registrar: registrar), registry: registrar.textures())
  70 + registrar.addMethodCallDelegate(instance, channel: channel)
72 } 71 }
73 72
74 public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) { 73 public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
@@ -96,13 +95,15 @@ public class SwiftMobileScannerPlugin: NSObject, FlutterPlugin { @@ -96,13 +95,15 @@ public class SwiftMobileScannerPlugin: NSObject, FlutterPlugin {
96 } 95 }
97 } 96 }
98 97
99 - /// Parses all parameters and starts the mobileScanner 98 + /// Start the mobileScanner.
100 private func start(_ call: FlutterMethodCall, _ result: @escaping FlutterResult) { 99 private func start(_ call: FlutterMethodCall, _ result: @escaping FlutterResult) {
101 let torch: Bool = (call.arguments as! Dictionary<String, Any?>)["torch"] as? Bool ?? false 100 let torch: Bool = (call.arguments as! Dictionary<String, Any?>)["torch"] as? Bool ?? false
102 let facing: Int = (call.arguments as! Dictionary<String, Any?>)["facing"] as? Int ?? 1 101 let facing: Int = (call.arguments as! Dictionary<String, Any?>)["facing"] as? Int ?? 1
103 let formats: Array<Int> = (call.arguments as! Dictionary<String, Any?>)["formats"] as? Array ?? [] 102 let formats: Array<Int> = (call.arguments as! Dictionary<String, Any?>)["formats"] as? Array ?? []
104 let returnImage: Bool = (call.arguments as! Dictionary<String, Any?>)["returnImage"] as? Bool ?? false 103 let returnImage: Bool = (call.arguments as! Dictionary<String, Any?>)["returnImage"] as? Bool ?? false
105 let speed: Int = (call.arguments as! Dictionary<String, Any?>)["speed"] as? Int ?? 0 104 let speed: Int = (call.arguments as! Dictionary<String, Any?>)["speed"] as? Int ?? 0
  105 + let timeoutMs: Int = (call.arguments as! Dictionary<String, Any?>)["timeout"] as? Int ?? 0
  106 + self.mobileScanner.timeoutSeconds = Double(timeoutMs) / Double(1000)
106 107
107 let formatList = formats.map { format in return BarcodeFormat(rawValue: format)} 108 let formatList = formats.map { format in return BarcodeFormat(rawValue: format)}
108 var barcodeOptions: BarcodeScannerOptions? = nil 109 var barcodeOptions: BarcodeScannerOptions? = nil
@@ -115,13 +116,17 @@ public class SwiftMobileScannerPlugin: NSObject, FlutterPlugin { @@ -115,13 +116,17 @@ public class SwiftMobileScannerPlugin: NSObject, FlutterPlugin {
115 barcodeOptions = BarcodeScannerOptions(formats: barcodeFormats) 116 barcodeOptions = BarcodeScannerOptions(formats: barcodeFormats)
116 } 117 }
117 118
118 -  
119 let position = facing == 0 ? AVCaptureDevice.Position.front : .back 119 let position = facing == 0 ? AVCaptureDevice.Position.front : .back
120 let detectionSpeed: DetectionSpeed = DetectionSpeed(rawValue: speed)! 120 let detectionSpeed: DetectionSpeed = DetectionSpeed(rawValue: speed)!
121 121
122 do { 122 do {
123 - try mobileScanner.start(barcodeScannerOptions: barcodeOptions, returnImage: returnImage, cameraPosition: position, torch: torch ? .on : .off, detectionSpeed: detectionSpeed) { parameters in  
124 - result(["textureId": parameters.textureId, "size": ["width": parameters.width, "height": parameters.height], "torchable": parameters.hasTorch]) 123 + try mobileScanner.start(barcodeScannerOptions: barcodeOptions, returnImage: returnImage, cameraPosition: position, torch: torch, detectionSpeed: detectionSpeed) { parameters in
  124 + DispatchQueue.main.async {
  125 + result([
  126 + "textureId": parameters.textureId,
  127 + "size": ["width": parameters.width, "height": parameters.height],
  128 + "torchable": parameters.hasTorch])
  129 + }
125 } 130 }
126 } catch MobileScannerError.alreadyStarted { 131 } catch MobileScannerError.alreadyStarted {
127 result(FlutterError(code: "MobileScanner", 132 result(FlutterError(code: "MobileScanner",
@@ -131,22 +136,18 @@ public class SwiftMobileScannerPlugin: NSObject, FlutterPlugin { @@ -131,22 +136,18 @@ public class SwiftMobileScannerPlugin: NSObject, FlutterPlugin {
131 result(FlutterError(code: "MobileScanner", 136 result(FlutterError(code: "MobileScanner",
132 message: "No camera found or failed to open camera!", 137 message: "No camera found or failed to open camera!",
133 details: nil)) 138 details: nil))
134 - } catch MobileScannerError.torchError(let error) {  
135 - result(FlutterError(code: "MobileScanner",  
136 - message: "Error occured when setting torch!",  
137 - details: error))  
138 } catch MobileScannerError.cameraError(let error) { 139 } catch MobileScannerError.cameraError(let error) {
139 result(FlutterError(code: "MobileScanner", 140 result(FlutterError(code: "MobileScanner",
140 message: "Error occured when setting up camera!", 141 message: "Error occured when setting up camera!",
141 details: error)) 142 details: error))
142 } catch { 143 } catch {
143 result(FlutterError(code: "MobileScanner", 144 result(FlutterError(code: "MobileScanner",
144 - message: "Unknown error occured..", 145 + message: "Unknown error occured.",
145 details: nil)) 146 details: nil))
146 } 147 }
147 } 148 }
148 149
149 - /// Stops the mobileScanner and closes the texture 150 + /// Stops the mobileScanner and closes the texture.
150 private func stop(_ result: @escaping FlutterResult) { 151 private func stop(_ result: @escaping FlutterResult) {
151 do { 152 do {
152 try mobileScanner.stop() 153 try mobileScanner.stop()
@@ -154,21 +155,19 @@ public class SwiftMobileScannerPlugin: NSObject, FlutterPlugin { @@ -154,21 +155,19 @@ public class SwiftMobileScannerPlugin: NSObject, FlutterPlugin {
154 result(nil) 155 result(nil)
155 } 156 }
156 157
157 - /// Toggles the torch 158 + /// Toggles the torch.
158 private func toggleTorch(_ call: FlutterMethodCall, _ result: @escaping FlutterResult) { 159 private func toggleTorch(_ call: FlutterMethodCall, _ result: @escaping FlutterResult) {
159 do { 160 do {
160 try mobileScanner.toggleTorch(call.arguments as? Int == 1 ? .on : .off) 161 try mobileScanner.toggleTorch(call.arguments as? Int == 1 ? .on : .off)
  162 + result(nil)
161 } catch { 163 } catch {
162 - result(FlutterError(code: "MobileScanner",  
163 - message: "Called toggleTorch() while stopped!",  
164 - details: nil)) 164 + result(FlutterError(code: "MobileScanner", message: error.localizedDescription, details: nil))
165 } 165 }
166 - result(nil)  
167 } 166 }
168 167
169 - /// Toggles the zoomScale 168 + /// Sets the zoomScale.
170 private func setScale(_ call: FlutterMethodCall, _ result: @escaping FlutterResult) { 169 private func setScale(_ call: FlutterMethodCall, _ result: @escaping FlutterResult) {
171 - var scale = call.arguments as? CGFloat 170 + let scale = call.arguments as? CGFloat
172 if (scale == nil) { 171 if (scale == nil) {
173 result(FlutterError(code: "MobileScanner", 172 result(FlutterError(code: "MobileScanner",
174 message: "You must provide a scale when calling setScale!", 173 message: "You must provide a scale when calling setScale!",
@@ -177,6 +176,7 @@ public class SwiftMobileScannerPlugin: NSObject, FlutterPlugin { @@ -177,6 +176,7 @@ public class SwiftMobileScannerPlugin: NSObject, FlutterPlugin {
177 } 176 }
178 do { 177 do {
179 try mobileScanner.setScale(scale!) 178 try mobileScanner.setScale(scale!)
  179 + result(nil)
180 } catch MobileScannerError.zoomWhenStopped { 180 } catch MobileScannerError.zoomWhenStopped {
181 result(FlutterError(code: "MobileScanner", 181 result(FlutterError(code: "MobileScanner",
182 message: "Called setScale() while stopped!", 182 message: "Called setScale() while stopped!",
@@ -190,13 +190,13 @@ public class SwiftMobileScannerPlugin: NSObject, FlutterPlugin { @@ -190,13 +190,13 @@ public class SwiftMobileScannerPlugin: NSObject, FlutterPlugin {
190 message: "Error while zooming.", 190 message: "Error while zooming.",
191 details: nil)) 191 details: nil))
192 } 192 }
193 - result(nil)  
194 } 193 }
195 194
196 - /// Reset the zoomScale 195 + /// Reset the zoomScale.
197 private func resetScale(_ call: FlutterMethodCall, _ result: @escaping FlutterResult) { 196 private func resetScale(_ call: FlutterMethodCall, _ result: @escaping FlutterResult) {
198 do { 197 do {
199 try mobileScanner.resetScale() 198 try mobileScanner.resetScale()
  199 + result(nil)
200 } catch MobileScannerError.zoomWhenStopped { 200 } catch MobileScannerError.zoomWhenStopped {
201 result(FlutterError(code: "MobileScanner", 201 result(FlutterError(code: "MobileScanner",
202 message: "Called resetScale() while stopped!", 202 message: "Called resetScale() while stopped!",
@@ -210,14 +210,12 @@ public class SwiftMobileScannerPlugin: NSObject, FlutterPlugin { @@ -210,14 +210,12 @@ public class SwiftMobileScannerPlugin: NSObject, FlutterPlugin {
210 message: "Error while zooming.", 210 message: "Error while zooming.",
211 details: nil)) 211 details: nil))
212 } 212 }
213 - result(nil)  
214 } 213 }
215 214
216 -  
217 - /// Toggles the torch 215 + /// Updates the scan window rectangle.
218 func updateScanWindow(_ call: FlutterMethodCall, _ result: @escaping FlutterResult) { 216 func updateScanWindow(_ call: FlutterMethodCall, _ result: @escaping FlutterResult) {
219 let scanWindowData: Array? = (call.arguments as? [String: Any])?["rect"] as? [CGFloat] 217 let scanWindowData: Array? = (call.arguments as? [String: Any])?["rect"] as? [CGFloat]
220 - SwiftMobileScannerPlugin.scanWindow = scanWindowData 218 + MobileScannerPlugin.scanWindow = scanWindowData
221 219
222 result(nil) 220 result(nil)
223 } 221 }
@@ -236,7 +234,7 @@ public class SwiftMobileScannerPlugin: NSObject, FlutterPlugin { @@ -236,7 +234,7 @@ public class SwiftMobileScannerPlugin: NSObject, FlutterPlugin {
236 return CGRect(x: minX, y: minY, width: width, height: height) 234 return CGRect(x: minX, y: minY, width: width, height: height)
237 } 235 }
238 236
239 - /// Analyzes a single image 237 + /// Analyzes a single image.
240 private func analyzeImage(_ call: FlutterMethodCall, _ result: @escaping FlutterResult) { 238 private func analyzeImage(_ call: FlutterMethodCall, _ result: @escaping FlutterResult) {
241 let uiImage = UIImage(contentsOfFile: call.arguments as? String ?? "") 239 let uiImage = UIImage(contentsOfFile: call.arguments as? String ?? "")
242 240
@@ -248,16 +246,28 @@ public class SwiftMobileScannerPlugin: NSObject, FlutterPlugin { @@ -248,16 +246,28 @@ public class SwiftMobileScannerPlugin: NSObject, FlutterPlugin {
248 } 246 }
249 247
250 mobileScanner.analyzeImage(image: uiImage!, position: AVCaptureDevice.Position.back, callback: { [self] barcodes, error in 248 mobileScanner.analyzeImage(image: uiImage!, position: AVCaptureDevice.Position.back, callback: { [self] barcodes, error in
251 - if error == nil && barcodes != nil && !barcodes!.isEmpty { 249 + if error != nil {
  250 + barcodeHandler.publishEvent(["name": "error", "message": error?.localizedDescription])
  251 +
  252 + DispatchQueue.main.async {
  253 + result(false)
  254 + }
  255 +
  256 + return
  257 + }
  258 +
  259 + if (barcodes == nil || barcodes!.isEmpty) {
  260 + DispatchQueue.main.async {
  261 + result(false)
  262 + }
  263 + } else {
252 let barcodesMap: [Any?] = barcodes!.compactMap { barcode in barcode.data } 264 let barcodesMap: [Any?] = barcodes!.compactMap { barcode in barcode.data }
253 let event: [String: Any?] = ["name": "barcode", "data": barcodesMap] 265 let event: [String: Any?] = ["name": "barcode", "data": barcodesMap]
254 barcodeHandler.publishEvent(event) 266 barcodeHandler.publishEvent(event)
  267 +
  268 + DispatchQueue.main.async {
255 result(true) 269 result(true)
256 - } else {  
257 - if error != nil {  
258 - barcodeHandler.publishEvent(["name": "error", "message": error?.localizedDescription])  
259 } 270 }
260 - result(false)  
261 } 271 }
262 }) 272 })
263 } 273 }
1 -//  
2 -// Util.swift  
3 -// camerax  
4 -//  
5 -// Created by 闫守旺 on 2021/2/6.  
6 -//  
7 -  
8 import AVFoundation 1 import AVFoundation
9 -import Flutter  
10 import Foundation 2 import Foundation
11 import MLKitBarcodeScanning 3 import MLKitBarcodeScanning
12 4
13 -extension Error {  
14 - func throwNative(_ result: FlutterResult) {  
15 - let error = FlutterError(code: localizedDescription, message: nil, details: nil)  
16 - result(error)  
17 - }  
18 -}  
19 -  
20 extension CVBuffer { 5 extension CVBuffer {
21 var image: UIImage { 6 var image: UIImage {
22 let ciImage = CIImage(cvPixelBuffer: self) 7 let ciImage = CIImage(cvPixelBuffer: self)
23 let cgImage = CIContext().createCGImage(ciImage, from: ciImage.extent) 8 let cgImage = CIContext().createCGImage(ciImage, from: ciImage.extent)
24 - return UIImage(cgImage: cgImage!, scale: 1.0, orientation: UIImage.Orientation.left) 9 + return UIImage(cgImage: cgImage!)
25 } 10 }
26 11
27 var image1: UIImage { 12 var image1: UIImage {
@@ -4,7 +4,7 @@ @@ -4,7 +4,7 @@
4 # 4 #
5 Pod::Spec.new do |s| 5 Pod::Spec.new do |s|
6 s.name = 'mobile_scanner' 6 s.name = 'mobile_scanner'
7 - s.version = '3.2.0' 7 + s.version = '3.5.5'
8 s.summary = 'An universal scanner for Flutter based on MLKit.' 8 s.summary = 'An universal scanner for Flutter based on MLKit.'
9 s.description = <<-DESC 9 s.description = <<-DESC
10 An universal scanner for Flutter based on MLKit. 10 An universal scanner for Flutter based on MLKit.
  1 +export 'src/enums/address_type.dart';
  2 +export 'src/enums/barcode_format.dart';
  3 +export 'src/enums/barcode_type.dart';
1 export 'src/enums/camera_facing.dart'; 4 export 'src/enums/camera_facing.dart';
2 export 'src/enums/detection_speed.dart'; 5 export 'src/enums/detection_speed.dart';
  6 +export 'src/enums/email_type.dart';
  7 +export 'src/enums/encryption_type.dart';
3 export 'src/enums/mobile_scanner_error_code.dart'; 8 export 'src/enums/mobile_scanner_error_code.dart';
4 export 'src/enums/mobile_scanner_state.dart'; 9 export 'src/enums/mobile_scanner_state.dart';
5 -export 'src/enums/ratio.dart'; 10 +export 'src/enums/phone_type.dart';
6 export 'src/enums/torch_state.dart'; 11 export 'src/enums/torch_state.dart';
7 export 'src/mobile_scanner.dart'; 12 export 'src/mobile_scanner.dart';
8 export 'src/mobile_scanner_controller.dart'; 13 export 'src/mobile_scanner_controller.dart';
9 export 'src/mobile_scanner_exception.dart'; 14 export 'src/mobile_scanner_exception.dart';
  15 +export 'src/objects/address.dart';
10 export 'src/objects/barcode.dart'; 16 export 'src/objects/barcode.dart';
11 export 'src/objects/barcode_capture.dart'; 17 export 'src/objects/barcode_capture.dart';
  18 +export 'src/objects/calendar_event.dart';
  19 +export 'src/objects/contact_info.dart';
  20 +export 'src/objects/driver_license.dart';
  21 +export 'src/objects/email.dart';
  22 +export 'src/objects/geo_point.dart';
12 export 'src/objects/mobile_scanner_arguments.dart'; 23 export 'src/objects/mobile_scanner_arguments.dart';
  24 +export 'src/objects/person_name.dart';
  25 +export 'src/objects/phone.dart';
  26 +export 'src/objects/sms.dart';
  27 +export 'src/objects/url_bookmark.dart';
  28 +export 'src/objects/wifi.dart';
@@ -5,9 +5,8 @@ import 'dart:ui' as ui; @@ -5,9 +5,8 @@ import 'dart:ui' as ui;
5 import 'package:flutter/services.dart'; 5 import 'package:flutter/services.dart';
6 import 'package:flutter_web_plugins/flutter_web_plugins.dart'; 6 import 'package:flutter_web_plugins/flutter_web_plugins.dart';
7 import 'package:mobile_scanner/mobile_scanner_web.dart'; 7 import 'package:mobile_scanner/mobile_scanner_web.dart';
8 -import 'package:mobile_scanner/src/barcode_utility.dart'; 8 +import 'package:mobile_scanner/src/enums/barcode_format.dart';
9 import 'package:mobile_scanner/src/enums/camera_facing.dart'; 9 import 'package:mobile_scanner/src/enums/camera_facing.dart';
10 -import 'package:mobile_scanner/src/objects/barcode.dart';  
11 10
12 /// This plugin is the web implementation of mobile_scanner. 11 /// This plugin is the web implementation of mobile_scanner.
13 /// It only supports QR codes. 12 /// It only supports QR codes.
@@ -110,7 +109,7 @@ class MobileScannerWebPlugin { @@ -110,7 +109,7 @@ class MobileScannerWebPlugin {
110 if (arguments.containsKey('formats')) { 109 if (arguments.containsKey('formats')) {
111 formats = (arguments['formats'] as List) 110 formats = (arguments['formats'] as List)
112 .cast<int>() 111 .cast<int>()
113 - .map((e) => toFormat(e)) 112 + .map(BarcodeFormat.fromRawValue)
114 .toList(); 113 .toList();
115 } 114 }
116 115
@@ -130,8 +129,6 @@ class MobileScannerWebPlugin { @@ -130,8 +129,6 @@ class MobileScannerWebPlugin {
130 _barCodeStreamSubscription = 129 _barCodeStreamSubscription =
131 barCodeReader.detectBarcodeContinuously().listen((code) { 130 barCodeReader.detectBarcodeContinuously().listen((code) {
132 if (code != null) { 131 if (code != null) {
133 - final List<Offset>? corners = code.corners;  
134 -  
135 controller.add({ 132 controller.add({
136 'name': 'barcodeWeb', 133 'name': 'barcodeWeb',
137 'data': { 134 'data': {
@@ -139,9 +136,9 @@ class MobileScannerWebPlugin { @@ -139,9 +136,9 @@ class MobileScannerWebPlugin {
139 'rawBytes': code.rawBytes, 136 'rawBytes': code.rawBytes,
140 'format': code.format.rawValue, 137 'format': code.format.rawValue,
141 'displayValue': code.displayValue, 138 'displayValue': code.displayValue,
142 - 'type': code.type.index,  
143 - if (corners != null && corners.isNotEmpty)  
144 - 'corners': corners 139 + 'type': code.type.rawValue,
  140 + if (code.corners.isNotEmpty)
  141 + 'corners': code.corners
145 .map( 142 .map(
146 (Offset c) => <Object?, Object?>{'x': c.dx, 'y': c.dy}, 143 (Offset c) => <Object?, Object?>{'x': c.dx, 'y': c.dy},
147 ) 144 )
@@ -152,9 +149,10 @@ class MobileScannerWebPlugin { @@ -152,9 +149,10 @@ class MobileScannerWebPlugin {
152 }); 149 });
153 150
154 final hasTorch = await barCodeReader.hasTorch(); 151 final hasTorch = await barCodeReader.hasTorch();
  152 + final bool? enableTorch = arguments['torch'] as bool?;
155 153
156 - if (hasTorch && arguments.containsKey('torch')) {  
157 - await barCodeReader.toggleTorch(enabled: arguments['torch'] as bool); 154 + if (hasTorch && enableTorch != null) {
  155 + await barCodeReader.toggleTorch(enabled: enableTorch);
158 } 156 }
159 157
160 return { 158 return {
1 -import 'dart:math' as math;  
2 -  
3 -import 'package:flutter/material.dart';  
4 -import 'package:mobile_scanner/mobile_scanner.dart';  
5 -  
6 -Size toSize(Map data) {  
7 - final width = data['width'] as double;  
8 - final height = data['height'] as double;  
9 - return Size(width, height);  
10 -}  
11 -  
12 -List<Offset>? toCorners(List<Map<Object?, Object?>>? data) {  
13 - if (data == null) {  
14 - return null;  
15 - }  
16 -  
17 - return List.unmodifiable(  
18 - data.map((Map<Object?, Object?> e) {  
19 - return Offset(e['x']! as double, e['y']! as double);  
20 - }),  
21 - );  
22 -}  
23 -  
24 -BarcodeFormat toFormat(int value) {  
25 - switch (value) {  
26 - case 0:  
27 - return BarcodeFormat.all;  
28 - case 1:  
29 - return BarcodeFormat.code128;  
30 - case 2:  
31 - return BarcodeFormat.code39;  
32 - case 4:  
33 - return BarcodeFormat.code93;  
34 - case 8:  
35 - return BarcodeFormat.codebar;  
36 - case 16:  
37 - return BarcodeFormat.dataMatrix;  
38 - case 32:  
39 - return BarcodeFormat.ean13;  
40 - case 64:  
41 - return BarcodeFormat.ean8;  
42 - case 128:  
43 - return BarcodeFormat.itf;  
44 - case 256:  
45 - return BarcodeFormat.qrCode;  
46 - case 512:  
47 - return BarcodeFormat.upcA;  
48 - case 1024:  
49 - return BarcodeFormat.upcE;  
50 - case 2048:  
51 - return BarcodeFormat.pdf417;  
52 - case 4096:  
53 - return BarcodeFormat.aztec;  
54 - default:  
55 - return BarcodeFormat.unknown;  
56 - }  
57 -}  
58 -  
59 -CalendarEvent? toCalendarEvent(Map? data) {  
60 - if (data != null) {  
61 - return CalendarEvent.fromNative(data);  
62 - } else {  
63 - return null;  
64 - }  
65 -}  
66 -  
67 -DateTime? toDateTime(Map<String, dynamic>? data) {  
68 - if (data != null) {  
69 - final year = data['year'] as int;  
70 - final month = data['month'] as int;  
71 - final day = data['day'] as int;  
72 - final hour = data['hours'] as int;  
73 - final minute = data['minutes'] as int;  
74 - final second = data['seconds'] as int;  
75 - return data['isUtc'] as bool  
76 - ? DateTime.utc(year, month, day, hour, minute, second)  
77 - : DateTime(year, month, day, hour, minute, second);  
78 - } else {  
79 - return null;  
80 - }  
81 -}  
82 -  
83 -ContactInfo? toContactInfo(Map? data) {  
84 - if (data != null) {  
85 - return ContactInfo.fromNative(data);  
86 - } else {  
87 - return null;  
88 - }  
89 -}  
90 -  
91 -PersonName? toName(Map? data) {  
92 - if (data != null) {  
93 - return PersonName.fromNative(data);  
94 - } else {  
95 - return null;  
96 - }  
97 -}  
98 -  
99 -DriverLicense? toDriverLicense(Map? data) {  
100 - if (data != null) {  
101 - return DriverLicense.fromNative(data);  
102 - } else {  
103 - return null;  
104 - }  
105 -}  
106 -  
107 -Email? toEmail(Map? data) {  
108 - if (data != null) {  
109 - return Email.fromNative(data);  
110 - } else {  
111 - return null;  
112 - }  
113 -}  
114 -  
115 -GeoPoint? toGeoPoint(Map? data) {  
116 - if (data != null) {  
117 - return GeoPoint.fromNative(data);  
118 - } else {  
119 - return null;  
120 - }  
121 -}  
122 -  
123 -Phone? toPhone(Map? data) {  
124 - if (data != null) {  
125 - return Phone.fromNative(data);  
126 - } else {  
127 - return null;  
128 - }  
129 -}  
130 -  
131 -SMS? toSMS(Map? data) {  
132 - if (data != null) {  
133 - return SMS.fromNative(data);  
134 - } else {  
135 - return null;  
136 - }  
137 -}  
138 -  
139 -UrlBookmark? toUrl(Map? data) {  
140 - if (data != null) {  
141 - return UrlBookmark.fromNative(data);  
142 - } else {  
143 - return null;  
144 - }  
145 -}  
146 -  
147 -WiFi? toWiFi(Map? data) {  
148 - if (data != null) {  
149 - return WiFi.fromNative(data);  
150 - } else {  
151 - return null;  
152 - }  
153 -}  
154 -  
155 -Size applyBoxFit(BoxFit fit, Size input, Size output) {  
156 - if (input.height <= 0.0 ||  
157 - input.width <= 0.0 ||  
158 - output.height <= 0.0 ||  
159 - output.width <= 0.0) {  
160 - return Size.zero;  
161 - }  
162 -  
163 - Size destination;  
164 -  
165 - final inputAspectRatio = input.width / input.height;  
166 - final outputAspectRatio = output.width / output.height;  
167 -  
168 - switch (fit) {  
169 - case BoxFit.fill:  
170 - destination = output;  
171 - break;  
172 - case BoxFit.contain:  
173 - if (outputAspectRatio > inputAspectRatio) {  
174 - destination = Size(  
175 - input.width * output.height / input.height,  
176 - output.height,  
177 - );  
178 - } else {  
179 - destination = Size(  
180 - output.width,  
181 - input.height * output.width / input.width,  
182 - );  
183 - }  
184 - break;  
185 -  
186 - case BoxFit.cover:  
187 - if (outputAspectRatio > inputAspectRatio) {  
188 - destination = Size(  
189 - output.width,  
190 - input.height * (output.width / input.width),  
191 - );  
192 - } else {  
193 - destination = Size(  
194 - input.width * (output.height / input.height),  
195 - output.height,  
196 - );  
197 - }  
198 - break;  
199 - case BoxFit.fitWidth:  
200 - destination = Size(  
201 - output.width,  
202 - input.height * (output.width / input.width),  
203 - );  
204 - break;  
205 - case BoxFit.fitHeight:  
206 - destination = Size(  
207 - input.width * (output.height / input.height),  
208 - output.height,  
209 - );  
210 - break;  
211 - case BoxFit.none:  
212 - destination = Size(  
213 - math.min(input.width, output.width),  
214 - math.min(input.height, output.height),  
215 - );  
216 - break;  
217 - case BoxFit.scaleDown:  
218 - destination = input;  
219 - if (destination.height > output.height) {  
220 - destination = Size(output.height * inputAspectRatio, output.height);  
221 - }  
222 - if (destination.width > output.width) {  
223 - destination = Size(output.width, output.width / inputAspectRatio);  
224 - }  
225 - break;  
226 - }  
227 -  
228 - return destination;  
229 -}  
  1 +/// Address type constants.
  2 +enum AddressType {
  3 + /// Unknown address type.
  4 + unknown(0),
  5 +
  6 + /// Work address.
  7 + work(1),
  8 +
  9 + /// Home address.
  10 + home(2);
  11 +
  12 + const AddressType(this.rawValue);
  13 +
  14 + factory AddressType.fromRawValue(int value) {
  15 + switch (value) {
  16 + case 0:
  17 + return AddressType.unknown;
  18 + case 1:
  19 + return AddressType.work;
  20 + case 2:
  21 + return AddressType.home;
  22 + default:
  23 + throw ArgumentError.value(value, 'value', 'Invalid raw value.');
  24 + }
  25 + }
  26 +
  27 + /// The raw address type value.
  28 + final int rawValue;
  29 +}
  1 +/// This enum defines the different barcode formats.
  2 +enum BarcodeFormat {
  3 + /// A barcode format that represents all unknown formats.
  4 + unknown(-1),
  5 +
  6 + /// A barcode format that represents all known formats.
  7 + all(0),
  8 +
  9 + /// Barcode format constant for Code 128.
  10 + code128(1),
  11 +
  12 + /// Barcode format constant for Code 39.
  13 + code39(2),
  14 +
  15 + /// Barcode format constant for Code 93.
  16 + code93(4),
  17 +
  18 + /// Barcode format constant for Codabar.
  19 + codabar(8),
  20 +
  21 + /// Barcode format constant for Data Matrix.
  22 + dataMatrix(16),
  23 +
  24 + /// Barcode format constant for EAN-13.
  25 + ean13(32),
  26 +
  27 + /// Barcode format constant for EAN-8.
  28 + ean8(64),
  29 +
  30 + /// Barcode format constant for ITF (Interleaved Two-of-Five).
  31 + itf(128),
  32 +
  33 + /// Barcode format constant for QR Codes.
  34 + qrCode(256),
  35 +
  36 + /// Barcode format constant for UPC-A.
  37 + upcA(512),
  38 +
  39 + /// Barcode format constant for UPC-E.
  40 + upcE(1024),
  41 +
  42 + /// Barcode format constant for PDF-417.
  43 + pdf417(2048),
  44 +
  45 + /// Barcode format constant for AZTEC.
  46 + aztec(4096);
  47 +
  48 + /// This constant represents the old value for [BarcodeFormat.codabar].
  49 + ///
  50 + /// Prefer using the new [BarcodeFormat.codabar] constant,
  51 + /// as the `codebar` value will be removed in a future release.
  52 + static const BarcodeFormat codebar = codabar;
  53 +
  54 + const BarcodeFormat(this.rawValue);
  55 +
  56 + factory BarcodeFormat.fromRawValue(int value) {
  57 + switch (value) {
  58 + case -1:
  59 + return BarcodeFormat.unknown;
  60 + case 0:
  61 + return BarcodeFormat.all;
  62 + case 1:
  63 + return BarcodeFormat.code128;
  64 + case 2:
  65 + return BarcodeFormat.code39;
  66 + case 4:
  67 + return BarcodeFormat.code93;
  68 + case 8:
  69 + return BarcodeFormat.codebar;
  70 + case 16:
  71 + return BarcodeFormat.dataMatrix;
  72 + case 32:
  73 + return BarcodeFormat.ean13;
  74 + case 64:
  75 + return BarcodeFormat.ean8;
  76 + case 128:
  77 + return BarcodeFormat.itf;
  78 + case 256:
  79 + return BarcodeFormat.qrCode;
  80 + case 512:
  81 + return BarcodeFormat.upcA;
  82 + case 1024:
  83 + return BarcodeFormat.upcE;
  84 + case 2048:
  85 + return BarcodeFormat.pdf417;
  86 + case 4096:
  87 + return BarcodeFormat.aztec;
  88 + default:
  89 + throw ArgumentError.value(value, 'value', 'Invalid raw value.');
  90 + }
  91 + }
  92 +
  93 + /// The raw value of the barcode format.
  94 + final int rawValue;
  95 +}
  1 +/// Barcode value type constants.
  2 +enum BarcodeType {
  3 + /// An unknown barcode type.
  4 + unknown(0),
  5 +
  6 + /// Barcode value type constant for contact information.
  7 + contactInfo(1),
  8 +
  9 + /// Barcode value type constant for email message details.
  10 + email(2),
  11 +
  12 + /// Barcode value type constant for ISBNs.
  13 + isbn(3),
  14 +
  15 + /// Barcode value type constant for phone numbers.
  16 + phone(4),
  17 +
  18 + /// Barcode value type constant for product codes.
  19 + product(5),
  20 +
  21 + /// Barcode value type constant for SMS details.
  22 + sms(6),
  23 +
  24 + /// Barcode value type constant for plain text.
  25 + text(7),
  26 +
  27 + /// Barcode value type constant for URLs or bookmarks.
  28 + url(8),
  29 +
  30 + /// Barcode value type constant for WiFi access point details.
  31 + wifi(9),
  32 +
  33 + /// Barcode value type constant for geographic coordinates.
  34 + geo(10),
  35 +
  36 + /// Barcode value type constant for calendar events.
  37 + calendarEvent(11),
  38 +
  39 + /// Barcode value type constant for driver license data.
  40 + driverLicense(12);
  41 +
  42 + const BarcodeType(this.rawValue);
  43 +
  44 + factory BarcodeType.fromRawValue(int value) {
  45 + switch (value) {
  46 + case 0:
  47 + return BarcodeType.unknown;
  48 + case 1:
  49 + return BarcodeType.contactInfo;
  50 + case 2:
  51 + return BarcodeType.email;
  52 + case 3:
  53 + return BarcodeType.isbn;
  54 + case 4:
  55 + return BarcodeType.phone;
  56 + case 5:
  57 + return BarcodeType.product;
  58 + case 6:
  59 + return BarcodeType.sms;
  60 + case 7:
  61 + return BarcodeType.text;
  62 + case 8:
  63 + return BarcodeType.url;
  64 + case 9:
  65 + return BarcodeType.wifi;
  66 + case 10:
  67 + return BarcodeType.geo;
  68 + case 11:
  69 + return BarcodeType.calendarEvent;
  70 + case 12:
  71 + return BarcodeType.driverLicense;
  72 + default:
  73 + throw ArgumentError.value(value, 'value', 'Invalid raw value.');
  74 + }
  75 + }
  76 +
  77 + /// The raw barcode type value.
  78 + final int rawValue;
  79 +}
1 /// The facing of a camera. 1 /// The facing of a camera.
2 enum CameraFacing { 2 enum CameraFacing {
3 /// Front facing camera. 3 /// Front facing camera.
4 - front, 4 + front(0),
5 5
6 /// Back facing camera. 6 /// Back facing camera.
7 - back, 7 + back(1);
  8 +
  9 + const CameraFacing(this.rawValue);
  10 +
  11 + factory CameraFacing.fromRawValue(int value) {
  12 + switch (value) {
  13 + case 0:
  14 + return CameraFacing.front;
  15 + case 1:
  16 + return CameraFacing.back;
  17 + default:
  18 + throw ArgumentError.value(value, 'value', 'Invalid raw value.');
  19 + }
  20 + }
  21 +
  22 + /// The raw value for the camera facing direction.
  23 + final int rawValue;
8 } 24 }
@@ -3,18 +3,34 @@ enum DetectionSpeed { @@ -3,18 +3,34 @@ 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 /// 5 ///
6 - /// NOTE: This mode does analyze every frame in order to check if the value  
7 - /// has changed.  
8 - noDuplicates, 6 + /// Bear in mind that this mode analyzes every frame,
  7 + /// in order to check if the value has changed.
  8 + noDuplicates(0),
9 9
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.  
14 - normal, 10 + /// The barcode scanner will scan barcodes,
  11 + /// while respecting the configured scan timeout between individual scans.
  12 + normal(1),
15 13
16 - /// Let the scanner detect barcodes without restriction. 14 + /// The barcode scanner will scan barcodes, without any restrictions.
17 /// 15 ///
18 - /// NOTE: This can cause memory issues with older devices.  
19 - unrestricted, 16 + /// Bear in mind that this mode can cause memory issues on older devices.
  17 + unrestricted(2);
  18 +
  19 + const DetectionSpeed(this.rawValue);
  20 +
  21 + factory DetectionSpeed.fromRawValue(int value) {
  22 + switch (value) {
  23 + case 0:
  24 + return DetectionSpeed.noDuplicates;
  25 + case 1:
  26 + return DetectionSpeed.normal;
  27 + case 2:
  28 + return DetectionSpeed.unrestricted;
  29 + default:
  30 + throw ArgumentError.value(value, 'value', 'Invalid raw value.');
  31 + }
  32 + }
  33 +
  34 + /// The raw value for the detection speed.
  35 + final int rawValue;
20 } 36 }
  1 +/// Email format type constants.
  2 +enum EmailType {
  3 + /// Unknown email type.
  4 + unknown(0),
  5 +
  6 + /// Work email.
  7 + work(1),
  8 +
  9 + /// Home email.
  10 + home(2);
  11 +
  12 + const EmailType(this.rawValue);
  13 +
  14 + factory EmailType.fromRawValue(int value) {
  15 + switch (value) {
  16 + case 0:
  17 + return EmailType.unknown;
  18 + case 1:
  19 + return EmailType.work;
  20 + case 2:
  21 + return EmailType.home;
  22 + default:
  23 + throw ArgumentError.value(value, 'value', 'Invalid raw value.');
  24 + }
  25 + }
  26 +
  27 + /// The raw email type value.
  28 + final int rawValue;
  29 +}
  1 +/// Wifi encryption type constants.
  2 +enum EncryptionType {
  3 + /// Unknown encryption type.
  4 + none(0),
  5 +
  6 + /// Not encrypted.
  7 + open(1),
  8 +
  9 + /// WPA level encryption.
  10 + wpa(2),
  11 +
  12 + /// WEP level encryption.
  13 + wep(3);
  14 +
  15 + const EncryptionType(this.rawValue);
  16 +
  17 + factory EncryptionType.fromRawValue(int value) {
  18 + switch (value) {
  19 + case 0:
  20 + return EncryptionType.none;
  21 + case 1:
  22 + return EncryptionType.open;
  23 + case 2:
  24 + return EncryptionType.wpa;
  25 + case 3:
  26 + return EncryptionType.wep;
  27 + default:
  28 + throw ArgumentError.value(value, 'value', 'Invalid raw value.');
  29 + }
  30 + }
  31 +
  32 + /// The raw value for the encryption type.
  33 + final int rawValue;
  34 +}
1 /// The authorization state of the scanner. 1 /// The authorization state of the scanner.
2 enum MobileScannerState { 2 enum MobileScannerState {
3 /// The scanner has not yet requested the required permissions. 3 /// The scanner has not yet requested the required permissions.
4 - undetermined, 4 + undetermined(0),
5 5
6 /// The scanner has the required permissions. 6 /// The scanner has the required permissions.
7 - authorized, 7 + authorized(1),
8 8
9 /// The user denied the required permissions. 9 /// The user denied the required permissions.
10 - denied 10 + denied(2);
  11 +
  12 + const MobileScannerState(this.rawValue);
  13 +
  14 + factory MobileScannerState.fromRawValue(int value) {
  15 + switch (value) {
  16 + case 0:
  17 + return MobileScannerState.undetermined;
  18 + case 1:
  19 + return MobileScannerState.authorized;
  20 + case 2:
  21 + return MobileScannerState.denied;
  22 + default:
  23 + throw ArgumentError.value(value, 'value', 'Invalid raw value.');
  24 + }
  25 + }
  26 +
  27 + /// The raw value for the state.
  28 + final int rawValue;
11 } 29 }
  1 +/// Phone number format type constants.
  2 +enum PhoneType {
  3 + /// Unknown phone type.
  4 + unknown(0),
  5 +
  6 + /// Work phone.
  7 + work(1),
  8 +
  9 + /// Home phone.
  10 + home(2),
  11 +
  12 + /// Fax machine.
  13 + fax(3),
  14 +
  15 + /// Mobile phone.
  16 + mobile(4);
  17 +
  18 + const PhoneType(this.rawValue);
  19 +
  20 + factory PhoneType.fromRawValue(int value) {
  21 + switch (value) {
  22 + case 0:
  23 + return PhoneType.unknown;
  24 + case 1:
  25 + return PhoneType.work;
  26 + case 2:
  27 + return PhoneType.home;
  28 + case 3:
  29 + return PhoneType.fax;
  30 + case 4:
  31 + return PhoneType.mobile;
  32 + default:
  33 + throw ArgumentError.value(value, 'value', 'Invalid raw value.');
  34 + }
  35 + }
  36 +
  37 + /// The raw phone type value.
  38 + final int rawValue;
  39 +}
1 -// This enum is not used yet  
2 -// enum Ratio { ratio_4_3, ratio_16_9 }  
1 -/// The state of torch. 1 +/// The state of the flashlight.
2 enum TorchState { 2 enum TorchState {
3 - /// Torch is off.  
4 - off, 3 + /// The flashlight is off.
  4 + off(0),
5 5
6 - /// Torch is on.  
7 - on, 6 + /// The flashlight is on.
  7 + on(1);
  8 +
  9 + const TorchState(this.rawValue);
  10 +
  11 + factory TorchState.fromRawValue(int value) {
  12 + switch (value) {
  13 + case 0:
  14 + return TorchState.off;
  15 + case 1:
  16 + return TorchState.on;
  17 + default:
  18 + throw ArgumentError.value(value, 'value', 'Invalid raw value.');
  19 + }
  20 + }
  21 +
  22 + /// The raw value for the torch state.
  23 + final int rawValue;
8 } 24 }
@@ -2,10 +2,13 @@ import 'dart:async'; @@ -2,10 +2,13 @@ import 'dart:async';
2 2
3 import 'package:flutter/foundation.dart'; 3 import 'package:flutter/foundation.dart';
4 import 'package:flutter/material.dart'; 4 import 'package:flutter/material.dart';
  5 +import 'package:flutter/services.dart';
  6 +import 'package:mobile_scanner/src/enums/mobile_scanner_error_code.dart';
5 import 'package:mobile_scanner/src/mobile_scanner_controller.dart'; 7 import 'package:mobile_scanner/src/mobile_scanner_controller.dart';
6 import 'package:mobile_scanner/src/mobile_scanner_exception.dart'; 8 import 'package:mobile_scanner/src/mobile_scanner_exception.dart';
7 import 'package:mobile_scanner/src/objects/barcode_capture.dart'; 9 import 'package:mobile_scanner/src/objects/barcode_capture.dart';
8 import 'package:mobile_scanner/src/objects/mobile_scanner_arguments.dart'; 10 import 'package:mobile_scanner/src/objects/mobile_scanner_arguments.dart';
  11 +import 'package:mobile_scanner/src/scan_window_calculation.dart';
9 12
10 /// The function signature for the error builder. 13 /// The function signature for the error builder.
11 typedef MobileScannerErrorBuilder = Widget Function( 14 typedef MobileScannerErrorBuilder = Widget Function(
@@ -63,7 +66,7 @@ class MobileScanner extends StatefulWidget { @@ -63,7 +66,7 @@ class MobileScanner extends StatefulWidget {
63 final bool startDelay; 66 final bool startDelay;
64 67
65 /// The overlay which will be painted above the scanner when has started successful. 68 /// The overlay which will be painted above the scanner when has started successful.
66 - /// Will no be pointed when an error occurs or the scanner hasn't be started yet. 69 + /// Will no be pointed when an error occurs or the scanner hasn't been started yet.
67 final Widget? overlay; 70 final Widget? overlay;
68 71
69 /// Create a new [MobileScanner] using the provided [controller] 72 /// Create a new [MobileScanner] using the provided [controller]
@@ -137,11 +140,31 @@ class _MobileScannerState extends State<MobileScanner> @@ -137,11 +140,31 @@ class _MobileScannerState extends State<MobileScanner>
137 widget.onStart?.call(arguments); 140 widget.onStart?.call(arguments);
138 widget.onScannerStarted?.call(arguments); 141 widget.onScannerStarted?.call(arguments);
139 }).catchError((error) { 142 }).catchError((error) {
140 - if (mounted) {  
141 - setState(() {  
142 - _startException = error as MobileScannerException;  
143 - }); 143 + if (!mounted) {
  144 + return;
  145 + }
  146 +
  147 + if (error is MobileScannerException) {
  148 + _startException = error;
  149 + } else if (error is PlatformException) {
  150 + _startException = MobileScannerException(
  151 + errorCode: MobileScannerErrorCode.genericError,
  152 + errorDetails: MobileScannerErrorDetails(
  153 + code: error.code,
  154 + message: error.message,
  155 + details: error.details,
  156 + ),
  157 + );
  158 + } else {
  159 + _startException = MobileScannerException(
  160 + errorCode: MobileScannerErrorCode.genericError,
  161 + errorDetails: MobileScannerErrorDetails(
  162 + details: error,
  163 + ),
  164 + );
144 } 165 }
  166 +
  167 + setState(() {});
145 }); 168 });
146 } 169 }
147 170
@@ -175,75 +198,6 @@ class _MobileScannerState extends State<MobileScanner> @@ -175,75 +198,6 @@ class _MobileScannerState extends State<MobileScanner>
175 } 198 }
176 } 199 }
177 200
178 - /// the [scanWindow] rect will be relative and scaled to the [widgetSize] not the texture. so it is possible,  
179 - /// depending on the [fit], for the [scanWindow] to partially or not at all overlap the [textureSize]  
180 - ///  
181 - /// since when using a [BoxFit] the content will always be centered on its parent. we can convert the rect  
182 - /// to be relative to the texture.  
183 - ///  
184 - /// since the textures size and the actuall image (on the texture size) might not be the same, we also need to  
185 - /// calculate the scanWindow in terms of percentages of the texture, not pixels.  
186 - Rect calculateScanWindowRelativeToTextureInPercentage(  
187 - BoxFit fit,  
188 - Rect scanWindow,  
189 - Size textureSize,  
190 - Size widgetSize,  
191 - ) {  
192 - double fittedTextureWidth;  
193 - double fittedTextureHeight;  
194 -  
195 - switch (fit) {  
196 - case BoxFit.contain:  
197 - final widthRatio = widgetSize.width / textureSize.width;  
198 - final heightRatio = widgetSize.height / textureSize.height;  
199 - final scale = widthRatio < heightRatio ? widthRatio : heightRatio;  
200 - fittedTextureWidth = textureSize.width * scale;  
201 - fittedTextureHeight = textureSize.height * scale;  
202 - break;  
203 -  
204 - case BoxFit.cover:  
205 - final widthRatio = widgetSize.width / textureSize.width;  
206 - final heightRatio = widgetSize.height / textureSize.height;  
207 - final scale = widthRatio > heightRatio ? widthRatio : heightRatio;  
208 - fittedTextureWidth = textureSize.width * scale;  
209 - fittedTextureHeight = textureSize.height * scale;  
210 - break;  
211 -  
212 - case BoxFit.fill:  
213 - fittedTextureWidth = widgetSize.width;  
214 - fittedTextureHeight = widgetSize.height;  
215 - break;  
216 -  
217 - case BoxFit.fitHeight:  
218 - final ratio = widgetSize.height / textureSize.height;  
219 - fittedTextureWidth = textureSize.width * ratio;  
220 - fittedTextureHeight = widgetSize.height;  
221 - break;  
222 -  
223 - case BoxFit.fitWidth:  
224 - final ratio = widgetSize.width / textureSize.width;  
225 - fittedTextureWidth = widgetSize.width;  
226 - fittedTextureHeight = textureSize.height * ratio;  
227 - break;  
228 -  
229 - case BoxFit.none:  
230 - case BoxFit.scaleDown:  
231 - fittedTextureWidth = textureSize.width;  
232 - fittedTextureHeight = textureSize.height;  
233 - break;  
234 - }  
235 -  
236 - final offsetX = (widgetSize.width - fittedTextureWidth) / 2;  
237 - final offsetY = (widgetSize.height - fittedTextureHeight) / 2;  
238 -  
239 - final left = (scanWindow.left - offsetX) / fittedTextureWidth;  
240 - final top = (scanWindow.top - offsetY) / fittedTextureHeight;  
241 - final right = (scanWindow.right - offsetX) / fittedTextureWidth;  
242 - final bottom = (scanWindow.bottom - offsetY) / fittedTextureHeight;  
243 -  
244 - return Rect.fromLTRB(left, top, right, bottom);  
245 - }  
246 -  
247 Rect? scanWindow; 201 Rect? scanWindow;
248 202
249 @override 203 @override
@@ -261,8 +215,8 @@ class _MobileScannerState extends State<MobileScanner> @@ -261,8 +215,8 @@ class _MobileScannerState extends State<MobileScanner>
261 scanWindow = calculateScanWindowRelativeToTextureInPercentage( 215 scanWindow = calculateScanWindowRelativeToTextureInPercentage(
262 widget.fit, 216 widget.fit,
263 widget.scanWindow!, 217 widget.scanWindow!,
264 - value.size,  
265 - Size(constraints.maxWidth, constraints.maxHeight), 218 + textureSize: value.size,
  219 + widgetSize: constraints.biggest,
266 ); 220 );
267 221
268 _controller.updateScanWindow(scanWindow); 222 _controller.updateScanWindow(scanWindow);
@@ -271,12 +225,22 @@ class _MobileScannerState extends State<MobileScanner> @@ -271,12 +225,22 @@ class _MobileScannerState extends State<MobileScanner>
271 return Stack( 225 return Stack(
272 alignment: Alignment.center, 226 alignment: Alignment.center,
273 children: [ 227 children: [
274 - _scanner(value.size, value.webId, value.textureId), 228 + _scanner(
  229 + value.size,
  230 + value.webId,
  231 + value.textureId,
  232 + value.nrOfCameras,
  233 + ),
275 widget.overlay!, 234 widget.overlay!,
276 ], 235 ],
277 ); 236 );
278 } else { 237 } else {
279 - return _scanner(value.size, value.webId, value.textureId); 238 + return _scanner(
  239 + value.size,
  240 + value.webId,
  241 + value.textureId,
  242 + value.nrOfCameras,
  243 + );
280 } 244 }
281 }, 245 },
282 ); 246 );
@@ -284,7 +248,7 @@ class _MobileScannerState extends State<MobileScanner> @@ -284,7 +248,7 @@ class _MobileScannerState extends State<MobileScanner>
284 ); 248 );
285 } 249 }
286 250
287 - Widget _scanner(Size size, String? webId, int? textureId) { 251 + Widget _scanner(Size size, String? webId, int? textureId, int? nrOfCameras) {
288 return ClipRect( 252 return ClipRect(
289 child: LayoutBuilder( 253 child: LayoutBuilder(
290 builder: (_, constraints) { 254 builder: (_, constraints) {
@@ -6,7 +6,6 @@ import 'package:flutter/cupertino.dart'; @@ -6,7 +6,6 @@ import 'package:flutter/cupertino.dart';
6 import 'package:flutter/foundation.dart'; 6 import 'package:flutter/foundation.dart';
7 import 'package:flutter/services.dart'; 7 import 'package:flutter/services.dart';
8 import 'package:mobile_scanner/mobile_scanner.dart'; 8 import 'package:mobile_scanner/mobile_scanner.dart';
9 -import 'package:mobile_scanner/src/barcode_utility.dart';  
10 9
11 /// The [MobileScannerController] holds all the logic of this plugin, 10 /// The [MobileScannerController] holds all the logic of this plugin,
12 /// where as the [MobileScanner] class is the frontend of this plugin. 11 /// where as the [MobileScanner] class is the frontend of this plugin.
@@ -23,6 +22,8 @@ class MobileScannerController { @@ -23,6 +22,8 @@ class MobileScannerController {
23 ) 22 )
24 this.onPermissionSet, 23 this.onPermissionSet,
25 this.autoStart = true, 24 this.autoStart = true,
  25 + this.cameraResolution,
  26 + this.useNewCameraSelector = false,
26 }); 27 });
27 28
28 /// Select which camera should be used. 29 /// Select which camera should be used.
@@ -48,19 +49,42 @@ class MobileScannerController { @@ -48,19 +49,42 @@ class MobileScannerController {
48 /// WARNING: DetectionSpeed.unrestricted can cause memory issues on some devices 49 /// WARNING: DetectionSpeed.unrestricted can cause memory issues on some devices
49 final DetectionSpeed detectionSpeed; 50 final DetectionSpeed detectionSpeed;
50 51
51 - /// Sets the timeout of scanner.  
52 - /// The timeout is set in miliseconds. 52 + /// Sets the timeout, in milliseconds, of the scanner.
53 /// 53 ///
54 - /// NOTE: The timeout only works if the [detectionSpeed] is set to  
55 - /// [DetectionSpeed.normal] (which is the default value). 54 + /// This timeout is ignored if the [detectionSpeed]
  55 + /// is not set to [DetectionSpeed.normal].
  56 + ///
  57 + /// By default this is set to `250` milliseconds,
  58 + /// which prevents memory issues on older devices.
56 final int detectionTimeoutMs; 59 final int detectionTimeoutMs;
57 60
58 /// Automatically start the mobileScanner on initialization. 61 /// Automatically start the mobileScanner on initialization.
59 final bool autoStart; 62 final bool autoStart;
60 63
  64 + /// The desired resolution for the camera.
  65 + ///
  66 + /// When this value is provided, the camera will try to match this resolution,
  67 + /// or fallback to the closest available resolution.
  68 + /// When this is null, Android defaults to a resolution of 640x480.
  69 + ///
  70 + /// Bear in mind that changing the resolution has an effect on the aspect ratio.
  71 + ///
  72 + /// When the camera orientation changes,
  73 + /// the resolution will be flipped to match the new dimensions of the display.
  74 + ///
  75 + /// Currently only supported on Android.
  76 + final Size? cameraResolution;
  77 +
  78 + /// Use the new resolution selector. Warning: not fully tested, may produce
  79 + /// unwanted/zoomed images.
  80 + ///
  81 + /// Only supported on Android
  82 + final bool useNewCameraSelector;
  83 +
61 /// Sets the barcode stream 84 /// Sets the barcode stream
62 final StreamController<BarcodeCapture> _barcodesController = 85 final StreamController<BarcodeCapture> _barcodesController =
63 StreamController.broadcast(); 86 StreamController.broadcast();
  87 +
64 Stream<BarcodeCapture> get barcodes => _barcodesController.stream; 88 Stream<BarcodeCapture> get barcodes => _barcodesController.stream;
65 89
66 static const MethodChannel _methodChannel = 90 static const MethodChannel _methodChannel =
@@ -114,10 +138,12 @@ class MobileScannerController { @@ -114,10 +138,12 @@ class MobileScannerController {
114 final Map<String, dynamic> arguments = {}; 138 final Map<String, dynamic> arguments = {};
115 139
116 cameraFacingState.value = cameraFacingOverride ?? facing; 140 cameraFacingState.value = cameraFacingOverride ?? facing;
117 - arguments['facing'] = cameraFacingState.value.index; 141 + arguments['facing'] = cameraFacingState.value.rawValue;
118 arguments['torch'] = torchEnabled; 142 arguments['torch'] = torchEnabled;
119 - arguments['speed'] = detectionSpeed.index; 143 + arguments['speed'] = detectionSpeed.rawValue;
120 arguments['timeout'] = detectionTimeoutMs; 144 arguments['timeout'] = detectionTimeoutMs;
  145 + arguments['returnImage'] = returnImage;
  146 + arguments['useNewCameraSelector'] = useNewCameraSelector;
121 147
122 /* if (scanWindow != null) { 148 /* if (scanWindow != null) {
123 arguments['scanWindow'] = [ 149 arguments['scanWindow'] = [
@@ -129,13 +155,18 @@ class MobileScannerController { @@ -129,13 +155,18 @@ class MobileScannerController {
129 } */ 155 } */
130 156
131 if (formats != null) { 157 if (formats != null) {
132 - if (kIsWeb || Platform.isIOS || Platform.isMacOS) { 158 + if (kIsWeb || Platform.isIOS || Platform.isMacOS || Platform.isAndroid) {
133 arguments['formats'] = formats!.map((e) => e.rawValue).toList(); 159 arguments['formats'] = formats!.map((e) => e.rawValue).toList();
134 - } else if (Platform.isAndroid) {  
135 - arguments['formats'] = formats!.map((e) => e.index).toList();  
136 } 160 }
137 } 161 }
138 - arguments['returnImage'] = returnImage; 162 +
  163 + if (cameraResolution != null) {
  164 + arguments['cameraResolution'] = <int>[
  165 + cameraResolution!.width.toInt(),
  166 + cameraResolution!.height.toInt(),
  167 + ];
  168 + }
  169 +
139 return arguments; 170 return arguments;
140 } 171 }
141 172
@@ -163,35 +194,53 @@ class MobileScannerController { @@ -163,35 +194,53 @@ class MobileScannerController {
163 194
164 // Check authorization status 195 // Check authorization status
165 if (!kIsWeb) { 196 if (!kIsWeb) {
166 - final MobileScannerState state = MobileScannerState  
167 - .values[await _methodChannel.invokeMethod('state') as int? ?? 0];  
168 - switch (state) {  
169 - case MobileScannerState.undetermined:  
170 - bool result = false; 197 + final MobileScannerState state;
171 198
172 try { 199 try {
173 - result =  
174 - await _methodChannel.invokeMethod('request') as bool? ?? false;  
175 - } catch (error) { 200 + state = MobileScannerState.fromRawValue(
  201 + await _methodChannel.invokeMethod('state') as int? ?? 0,
  202 + );
  203 + } on PlatformException catch (error) {
176 isStarting = false; 204 isStarting = false;
177 - throw const MobileScannerException( 205 +
  206 + throw MobileScannerException(
178 errorCode: MobileScannerErrorCode.genericError, 207 errorCode: MobileScannerErrorCode.genericError,
  208 + errorDetails: MobileScannerErrorDetails(
  209 + code: error.code,
  210 + details: error.details as Object?,
  211 + message: error.message,
  212 + ),
179 ); 213 );
180 } 214 }
181 215
182 - if (!result) { 216 + switch (state) {
  217 + // Android does not have an undetermined permission state.
  218 + // So if the permission state is denied, just request it now.
  219 + case MobileScannerState.undetermined:
  220 + case MobileScannerState.denied:
  221 + try {
  222 + final bool granted =
  223 + await _methodChannel.invokeMethod('request') as bool? ?? false;
  224 +
  225 + if (!granted) {
183 isStarting = false; 226 isStarting = false;
184 throw const MobileScannerException( 227 throw const MobileScannerException(
185 errorCode: MobileScannerErrorCode.permissionDenied, 228 errorCode: MobileScannerErrorCode.permissionDenied,
186 ); 229 );
187 } 230 }
188 -  
189 - break;  
190 - case MobileScannerState.denied: 231 + } on PlatformException catch (error) {
191 isStarting = false; 232 isStarting = false;
192 - throw const MobileScannerException(  
193 - errorCode: MobileScannerErrorCode.permissionDenied, 233 + throw MobileScannerException(
  234 + errorCode: MobileScannerErrorCode.genericError,
  235 + errorDetails: MobileScannerErrorDetails(
  236 + code: error.code,
  237 + details: error.details as Object?,
  238 + message: error.message,
  239 + ),
194 ); 240 );
  241 + }
  242 +
  243 + break;
195 case MobileScannerState.authorized: 244 case MobileScannerState.authorized:
196 break; 245 break;
197 } 246 }
@@ -243,18 +292,28 @@ class MobileScannerController { @@ -243,18 +292,28 @@ class MobileScannerController {
243 292
244 final hasTorch = startResult['torchable'] as bool? ?? false; 293 final hasTorch = startResult['torchable'] as bool? ?? false;
245 hasTorchState.value = hasTorch; 294 hasTorchState.value = hasTorch;
246 - if (hasTorch && torchEnabled) {  
247 - torchState.value = TorchState.on; 295 +
  296 + final Size size;
  297 +
  298 + if (kIsWeb) {
  299 + size = Size(
  300 + startResult['videoWidth'] as double? ?? 0,
  301 + startResult['videoHeight'] as double? ?? 0,
  302 + );
  303 + } else {
  304 + final Map<Object?, Object?>? sizeInfo =
  305 + startResult['size'] as Map<Object?, Object?>?;
  306 +
  307 + size = Size(
  308 + sizeInfo?['width'] as double? ?? 0,
  309 + sizeInfo?['height'] as double? ?? 0,
  310 + );
248 } 311 }
249 312
250 isStarting = false; 313 isStarting = false;
251 return startArguments.value = MobileScannerArguments( 314 return startArguments.value = MobileScannerArguments(
252 - size: kIsWeb  
253 - ? Size(  
254 - startResult['videoWidth'] as double? ?? 0,  
255 - startResult['videoHeight'] as double? ?? 0,  
256 - )  
257 - : toSize(startResult['size'] as Map? ?? {}), 315 + nrOfCameras: startResult['nrOfCameras'] as int?,
  316 + size: size,
258 hasTorch: hasTorch, 317 hasTorch: hasTorch,
259 textureId: kIsWeb ? null : startResult['textureId'] as int?, 318 textureId: kIsWeb ? null : startResult['textureId'] as int?,
260 webId: kIsWeb ? startResult['ViewID'] as String? : null, 319 webId: kIsWeb ? startResult['ViewID'] as String? : null,
@@ -263,11 +322,11 @@ class MobileScannerController { @@ -263,11 +322,11 @@ class MobileScannerController {
263 322
264 /// Stops the camera, but does not dispose this controller. 323 /// Stops the camera, but does not dispose this controller.
265 Future<void> stop() async { 324 Future<void> stop() async {
266 - try {  
267 await _methodChannel.invokeMethod('stop'); 325 await _methodChannel.invokeMethod('stop');
268 - } catch (e) {  
269 - debugPrint('$e');  
270 - } 326 +
  327 + // After the camera stopped, set the torch state to off,
  328 + // as the torch state callback is never called when the camera is stopped.
  329 + torchState.value = TorchState.off;
271 } 330 }
272 331
273 /// Switches the torch on or off. 332 /// Switches the torch on or off.
@@ -282,14 +341,16 @@ class MobileScannerController { @@ -282,14 +341,16 @@ class MobileScannerController {
282 throw const MobileScannerException( 341 throw const MobileScannerException(
283 errorCode: MobileScannerErrorCode.controllerUninitialized, 342 errorCode: MobileScannerErrorCode.controllerUninitialized,
284 ); 343 );
285 - } else if (!hasTorch) { 344 + }
  345 +
  346 + if (!hasTorch) {
286 return; 347 return;
287 } 348 }
288 349
289 - torchState.value = 350 + final TorchState newState =
290 torchState.value == TorchState.off ? TorchState.on : TorchState.off; 351 torchState.value == TorchState.off ? TorchState.on : TorchState.off;
291 352
292 - await _methodChannel.invokeMethod('torch', torchState.value.index); 353 + await _methodChannel.invokeMethod('torch', newState.rawValue);
293 } 354 }
294 355
295 /// Changes the state of the camera (front or back). 356 /// Changes the state of the camera (front or back).
@@ -384,6 +445,9 @@ class MobileScannerController { @@ -384,6 +445,9 @@ class MobileScannerController {
384 barcodes: [ 445 barcodes: [
385 Barcode( 446 Barcode(
386 rawValue: (data as Map)['payload'] as String?, 447 rawValue: (data as Map)['payload'] as String?,
  448 + format: BarcodeFormat.fromRawValue(
  449 + data['symbology'] as int? ?? -1,
  450 + ),
387 ), 451 ),
388 ], 452 ],
389 ), 453 ),
@@ -391,6 +455,8 @@ class MobileScannerController { @@ -391,6 +455,8 @@ class MobileScannerController {
391 break; 455 break;
392 case 'barcodeWeb': 456 case 'barcodeWeb':
393 final barcode = data as Map?; 457 final barcode = data as Map?;
  458 + final corners = barcode?['corners'] as List<Object?>? ?? <Object?>[];
  459 +
394 _barcodesController.add( 460 _barcodesController.add(
395 BarcodeCapture( 461 BarcodeCapture(
396 raw: data, 462 raw: data,
@@ -399,10 +465,15 @@ class MobileScannerController { @@ -399,10 +465,15 @@ class MobileScannerController {
399 Barcode( 465 Barcode(
400 rawValue: barcode['rawValue'] as String?, 466 rawValue: barcode['rawValue'] as String?,
401 rawBytes: barcode['rawBytes'] as Uint8List?, 467 rawBytes: barcode['rawBytes'] as Uint8List?,
402 - format: toFormat(barcode['format'] as int),  
403 - corners: toCorners(  
404 - (barcode['corners'] as List<Object?>? ?? [])  
405 - .cast<Map<Object?, Object?>>(), 468 + format: BarcodeFormat.fromRawValue(
  469 + barcode['format'] as int? ?? -1,
  470 + ),
  471 + corners: List.unmodifiable(
  472 + corners.cast<Map<Object?, Object?>>().map(
  473 + (Map<Object?, Object?> e) {
  474 + return Offset(e['x']! as double, e['y']! as double);
  475 + },
  476 + ),
406 ), 477 ),
407 ), 478 ),
408 ], 479 ],
  1 +import 'package:mobile_scanner/src/enums/address_type.dart';
  2 +
  3 +/// An address.
  4 +class Address {
  5 + /// Creates a new [Address] instance.
  6 + const Address({
  7 + this.addressLines = const <String>[],
  8 + this.type = AddressType.unknown,
  9 + });
  10 +
  11 + /// Creates a new [Address] instance from a map.
  12 + factory Address.fromNative(Map<Object?, Object?> data) {
  13 + final List<Object?>? addressLines = data['addressLines'] as List<Object?>?;
  14 + final AddressType type = AddressType.fromRawValue(
  15 + data['type'] as int? ?? 0,
  16 + );
  17 +
  18 + if (addressLines == null) {
  19 + return Address(type: type);
  20 + }
  21 +
  22 + return Address(
  23 + addressLines: List.unmodifiable(addressLines.cast<String>()),
  24 + type: type,
  25 + );
  26 + }
  27 +
  28 + /// The address lines that represent this address.
  29 + final List<String> addressLines;
  30 +
  31 + /// Gets type of the address.
  32 + final AddressType type;
  33 +}