Julian Steenbakker

Merge remote-tracking branch 'origin/master'

Showing 99 changed files with 1968 additions and 1131 deletions

Too many changes to show.

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

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