Julian Steenbakker

imp: apply camerax

@@ -9,7 +9,7 @@ buildscript { @@ -9,7 +9,7 @@ buildscript {
9 } 9 }
10 10
11 dependencies { 11 dependencies {
12 - classpath 'com.android.tools.build:gradle:4.1.0' 12 + classpath 'com.android.tools.build:gradle:4.1.3'
13 classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 13 classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
14 } 14 }
15 } 15 }
@@ -41,10 +41,12 @@ android { @@ -41,10 +41,12 @@ android {
41 } 41 }
42 42
43 defaultConfig { 43 defaultConfig {
44 - minSdkVersion 16 44 + minSdkVersion 21
45 } 45 }
46 } 46 }
47 47
48 dependencies { 48 dependencies {
49 implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 49 implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
  50 + implementation 'com.google.mlkit:barcode-scanning:17.0.2'
  51 + implementation 'com.google.mlkit:camera:16.0.0-beta3'
50 } 52 }
1 <manifest xmlns:android="http://schemas.android.com/apk/res/android" 1 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
2 package="dev.steenbakker.mobile_scanner"> 2 package="dev.steenbakker.mobile_scanner">
  3 +
  4 + <uses-permission android:name="android.permission.CAMERA" />
3 </manifest> 5 </manifest>
  1 +package dev.steenbakker.mobile_scanner
  2 +
  3 +import android.Manifest
  4 +import android.annotation.SuppressLint
  5 +import android.app.Activity
  6 +import android.content.pm.PackageManager
  7 +import android.util.Log
  8 +import android.view.Surface
  9 +import androidx.annotation.IntDef
  10 +import androidx.annotation.NonNull
  11 +import androidx.camera.core.*
  12 +import androidx.camera.lifecycle.ProcessCameraProvider
  13 +import androidx.core.app.ActivityCompat
  14 +import androidx.core.content.ContextCompat
  15 +import androidx.lifecycle.LifecycleOwner
  16 +import com.google.mlkit.vision.barcode.BarcodeScanning
  17 +import com.google.mlkit.vision.common.InputImage
  18 +import io.flutter.plugin.common.*
  19 +import io.flutter.view.TextureRegistry
  20 +
  21 +class MobileScanner(private val activity: Activity, private val textureRegistry: TextureRegistry)
  22 + : MethodChannel.MethodCallHandler, EventChannel.StreamHandler, PluginRegistry.RequestPermissionsResultListener {
  23 + companion object {
  24 + private const val REQUEST_CODE = 19930430
  25 + }
  26 +
  27 + private var sink: EventChannel.EventSink? = null
  28 + private var listener: PluginRegistry.RequestPermissionsResultListener? = null
  29 +
  30 + private var cameraProvider: ProcessCameraProvider? = null
  31 + private var camera: Camera? = null
  32 + private var textureEntry: TextureRegistry.SurfaceTextureEntry? = null
  33 +
  34 +// @AnalyzeMode
  35 +// private var analyzeMode: Int = AnalyzeMode.NONE
  36 +
  37 + @ExperimentalGetImage
  38 + override fun onMethodCall(@NonNull call: MethodCall, @NonNull result: MethodChannel.Result) {
  39 + when (call.method) {
  40 + "state" -> stateNative(result)
  41 + "request" -> requestNative(result)
  42 + "start" -> startNative(call, result)
  43 + "torch" -> torchNative(call, result)
  44 + "analyze" -> analyzeNative(call, result)
  45 + "stop" -> stopNative(result)
  46 + else -> result.notImplemented()
  47 + }
  48 + }
  49 +
  50 + override fun onListen(arguments: Any?, events: EventChannel.EventSink?) {
  51 + this.sink = events
  52 + }
  53 +
  54 + override fun onCancel(arguments: Any?) {
  55 + sink = null
  56 + }
  57 +
  58 + override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>?, grantResults: IntArray?): Boolean {
  59 + return listener?.onRequestPermissionsResult(requestCode, permissions, grantResults) ?: false
  60 + }
  61 +
  62 + private fun stateNative(result: MethodChannel.Result) {
  63 + // Can't get exact denied or not_determined state without request. Just return not_determined when state isn't authorized
  64 + val state =
  65 + if (ContextCompat.checkSelfPermission(activity, Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED) 1
  66 + else 0
  67 + result.success(state)
  68 + }
  69 +
  70 + private fun requestNative(result: MethodChannel.Result) {
  71 + listener = PluginRegistry.RequestPermissionsResultListener { requestCode, _, grantResults ->
  72 + if (requestCode != REQUEST_CODE) {
  73 + false
  74 + } else {
  75 + val authorized = grantResults[0] == PackageManager.PERMISSION_GRANTED
  76 + result.success(authorized)
  77 + listener = null
  78 + true
  79 + }
  80 + }
  81 + val permissions = arrayOf(Manifest.permission.CAMERA)
  82 + ActivityCompat.requestPermissions(activity, permissions, REQUEST_CODE)
  83 + }
  84 +
  85 + @ExperimentalGetImage
  86 + private fun startNative(call: MethodCall, result: MethodChannel.Result) {
  87 + val future = ProcessCameraProvider.getInstance(activity)
  88 + val executor = ContextCompat.getMainExecutor(activity)
  89 + future.addListener({
  90 + cameraProvider = future.get()
  91 + textureEntry = textureRegistry.createSurfaceTexture()
  92 + val textureId = textureEntry!!.id()
  93 + // Preview
  94 + val surfaceProvider = Preview.SurfaceProvider { request ->
  95 + val resolution = request.resolution
  96 + val texture = textureEntry!!.surfaceTexture()
  97 + texture.setDefaultBufferSize(resolution.width, resolution.height)
  98 + val surface = Surface(texture)
  99 + request.provideSurface(surface, executor, { })
  100 + }
  101 + val preview = Preview.Builder().build().apply { setSurfaceProvider(surfaceProvider) }
  102 + // Analyzer
  103 + val analyzer = ImageAnalysis.Analyzer { imageProxy -> // YUV_420_888 format
  104 +// when (analyzeMode) {
  105 +// AnalyzeMode.BARCODE -> {
  106 + val mediaImage = imageProxy.image ?: return@Analyzer
  107 + val inputImage = InputImage.fromMediaImage(mediaImage, imageProxy.imageInfo.rotationDegrees)
  108 + val scanner = BarcodeScanning.getClient()
  109 + scanner.process(inputImage)
  110 + .addOnSuccessListener { barcodes ->
  111 + for (barcode in barcodes) {
  112 + val event = mapOf("name" to "barcode", "data" to barcode.data)
  113 + sink?.success(event)
  114 + }
  115 + }
  116 + .addOnFailureListener { e -> Log.e(TAG, e.message, e) }
  117 + .addOnCompleteListener { imageProxy.close() }
  118 +// }
  119 +// else -> imageProxy.close()
  120 +// }
  121 + }
  122 + val analysis = ImageAnalysis.Builder()
  123 + .setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
  124 + .build().apply { setAnalyzer(executor, analyzer) }
  125 + // Bind to lifecycle.
  126 + val owner = activity as LifecycleOwner
  127 + val selector =
  128 + if (call.arguments == 0) CameraSelector.DEFAULT_FRONT_CAMERA
  129 + else CameraSelector.DEFAULT_BACK_CAMERA
  130 + camera = cameraProvider!!.bindToLifecycle(owner, selector, preview, analysis)
  131 + camera!!.cameraInfo.torchState.observe(owner, { state ->
  132 + // TorchState.OFF = 0; TorchState.ON = 1
  133 + val event = mapOf("name" to "torchState", "data" to state)
  134 + sink?.success(event)
  135 + })
  136 + // TODO: seems there's not a better way to get the final resolution
  137 + @SuppressLint("RestrictedApi")
  138 + val resolution = preview.attachedSurfaceResolution!!
  139 + val portrait = camera!!.cameraInfo.sensorRotationDegrees % 180 == 0
  140 + val width = resolution.width.toDouble()
  141 + val height = resolution.height.toDouble()
  142 + val size = if (portrait) mapOf("width" to width, "height" to height) else mapOf("width" to height, "height" to width)
  143 + val answer = mapOf("textureId" to textureId, "size" to size, "torchable" to camera!!.torchable)
  144 + result.success(answer)
  145 + }, executor)
  146 + }
  147 +
  148 + private fun torchNative(call: MethodCall, result: MethodChannel.Result) {
  149 + val state = call.arguments == 1
  150 + camera!!.cameraControl.enableTorch(state)
  151 + result.success(null)
  152 + }
  153 +
  154 + private fun analyzeNative(call: MethodCall, result: MethodChannel.Result) {
  155 +// analyzeMode = call.arguments as Int
  156 + result.success(null)
  157 + }
  158 +
  159 + private fun stopNative(result: MethodChannel.Result) {
  160 + val owner = activity as LifecycleOwner
  161 + camera!!.cameraInfo.torchState.removeObservers(owner)
  162 + cameraProvider!!.unbindAll()
  163 + textureEntry!!.release()
  164 +
  165 + camera = null
  166 + textureEntry = null
  167 + cameraProvider = null
  168 +
  169 + result.success(null)
  170 + }
  171 +}
  172 +//
  173 +//
  174 +//@IntDef(AnalyzeMode.NONE, AnalyzeMode.BARCODE)
  175 +//@Target(AnnotationTarget.FIELD)
  176 +//@Retention(AnnotationRetention.SOURCE)
  177 +//annotation class AnalyzeMode {
  178 +// companion object {
  179 +// const val NONE = 0
  180 +// const val BARCODE = 1
  181 +// }
  182 +//}
1 package dev.steenbakker.mobile_scanner 1 package dev.steenbakker.mobile_scanner
2 2
3 import androidx.annotation.NonNull 3 import androidx.annotation.NonNull
4 -  
5 import io.flutter.embedding.engine.plugins.FlutterPlugin 4 import io.flutter.embedding.engine.plugins.FlutterPlugin
6 -import io.flutter.plugin.common.MethodCall 5 +import io.flutter.embedding.engine.plugins.activity.ActivityAware
  6 +import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding
  7 +import io.flutter.plugin.common.EventChannel
7 import io.flutter.plugin.common.MethodChannel 8 import io.flutter.plugin.common.MethodChannel
8 -import io.flutter.plugin.common.MethodChannel.MethodCallHandler  
9 -import io.flutter.plugin.common.MethodChannel.Result  
10 -  
11 -/** MobileScannerPlugin */  
12 -class MobileScannerPlugin: FlutterPlugin, MethodCallHandler {  
13 - /// The MethodChannel that will the communication between Flutter and native Android  
14 - ///  
15 - /// This local reference serves to register the plugin with the Flutter Engine and unregister it  
16 - /// when the Flutter Engine is detached from the Activity  
17 - private lateinit var channel : MethodChannel  
18 -  
19 - override fun onAttachedToEngine(@NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) {  
20 - channel = MethodChannel(flutterPluginBinding.binaryMessenger, "mobile_scanner")  
21 - channel.setMethodCallHandler(this)  
22 - }  
23 -  
24 - override fun onMethodCall(@NonNull call: MethodCall, @NonNull result: Result) {  
25 - if (call.method == "getPlatformVersion") {  
26 - result.success("Android ${android.os.Build.VERSION.RELEASE}")  
27 - } else {  
28 - result.notImplemented() 9 +
  10 +/** CameraXPlugin */
  11 +class MobileScannerPlugin : FlutterPlugin, ActivityAware {
  12 + private var flutter: FlutterPlugin.FlutterPluginBinding? = null
  13 + private var activity: ActivityPluginBinding? = null
  14 + private var handler: MobileScanner? = null
  15 + private var method: MethodChannel? = null
  16 + private var event: EventChannel? = null
  17 +
  18 + override fun onAttachedToEngine(@NonNull binding: FlutterPlugin.FlutterPluginBinding) {
  19 + this.flutter = binding
  20 + }
  21 +
  22 + override fun onDetachedFromEngine(@NonNull binding: FlutterPlugin.FlutterPluginBinding) {
  23 + this.flutter = null
  24 + }
  25 +
  26 + override fun onAttachedToActivity(binding: ActivityPluginBinding) {
  27 + activity = binding
  28 + handler = MobileScanner(activity!!.activity, flutter!!.textureRegistry)
  29 + method = MethodChannel(flutter!!.binaryMessenger, "dev.steenbakker.mobile_scanner/scanner/method")
  30 + event = EventChannel(flutter!!.binaryMessenger, "dev.steenbakker.mobile_scanner/scanner/event")
  31 + method!!.setMethodCallHandler(handler)
  32 + event!!.setStreamHandler(handler)
  33 + activity!!.addRequestPermissionsResultListener(handler!!)
  34 + }
  35 +
  36 + override fun onReattachedToActivityForConfigChanges(binding: ActivityPluginBinding) {
  37 + onAttachedToActivity(binding)
29 } 38 }
30 - }  
31 39
32 - override fun onDetachedFromEngine(@NonNull binding: FlutterPlugin.FlutterPluginBinding) {  
33 - channel.setMethodCallHandler(null)  
34 - } 40 + override fun onDetachedFromActivity() {
  41 + activity!!.removeRequestPermissionsResultListener(handler!!)
  42 + event!!.setStreamHandler(null)
  43 + method!!.setMethodCallHandler(null)
  44 + event = null
  45 + method = null
  46 + handler = null
  47 + activity = null
  48 + }
  49 +
  50 + override fun onDetachedFromActivityForConfigChanges() {
  51 + onDetachedFromActivity()
  52 + }
35 } 53 }
  1 +package dev.steenbakker.mobile_scanner
  2 +
  3 +import android.graphics.Point
  4 +import androidx.camera.core.Camera
  5 +import androidx.camera.core.ImageProxy
  6 +import com.google.mlkit.vision.barcode.common.Barcode
  7 +
  8 +val Any.TAG: String
  9 + get() = javaClass.simpleName
  10 +
  11 +val Camera.torchable: Boolean
  12 + get() = cameraInfo.hasFlashUnit()
  13 +//
  14 +//val ImageProxy.yuv: ByteArray
  15 +// get() {
  16 +// val ySize = y.buffer.remaining()
  17 +// val uSize = u.buffer.remaining()
  18 +// val vSize = v.buffer.remaining()
  19 +//
  20 +// val size = ySize + uSize + vSize
  21 +// val data = ByteArray(size)
  22 +//
  23 +// var offset = 0
  24 +// y.buffer.get(data, offset, ySize)
  25 +// offset += ySize
  26 +// u.buffer.get(data, offset, uSize)
  27 +// offset += uSize
  28 +// v.buffer.get(data, offset, vSize)
  29 +//
  30 +// return data
  31 +// }
  32 +//
  33 +//val ImageProxy.nv21: ByteArray
  34 +// get() {
  35 +// if (BuildConfig.DEBUG) {
  36 +// if (y.pixelStride != 1 || u.rowStride != v.rowStride || u.pixelStride != v.pixelStride) {
  37 +// error("Assertion failed")
  38 +// }
  39 +// }
  40 +//
  41 +// val ySize = width * height
  42 +// val uvSize = ySize / 2
  43 +// val size = ySize + uvSize
  44 +// val data = ByteArray(size)
  45 +//
  46 +// var offset = 0
  47 +// // Y Plane
  48 +// if (y.rowStride == width) {
  49 +// y.buffer.get(data, offset, ySize)
  50 +// offset += ySize
  51 +// } else {
  52 +// for (row in 0 until height) {
  53 +// y.buffer.get(data, offset, width)
  54 +// offset += width
  55 +// }
  56 +//
  57 +// if (BuildConfig.DEBUG && offset != ySize) {
  58 +// error("Assertion failed")
  59 +// }
  60 +// }
  61 +// // U,V Planes
  62 +// if (v.rowStride == width && v.pixelStride == 2) {
  63 +// if (BuildConfig.DEBUG && v.size != uvSize - 1) {
  64 +// error("Assertion failed")
  65 +// }
  66 +//
  67 +// v.buffer.get(data, offset, 1)
  68 +// offset += 1
  69 +// u.buffer.get(data, offset, u.size)
  70 +// if (BuildConfig.DEBUG) {
  71 +// val value = v.buffer.get()
  72 +// if (data[offset] != value) {
  73 +// error("Assertion failed")
  74 +// }
  75 +// }
  76 +// } else {
  77 +// for (row in 0 until height / 2)
  78 +// for (col in 0 until width / 2) {
  79 +// val index = row * v.rowStride + col * v.pixelStride
  80 +// data[offset++] = v.buffer.get(index)
  81 +// data[offset++] = u.buffer.get(index)
  82 +// }
  83 +//
  84 +// if (BuildConfig.DEBUG && offset != size) {
  85 +// error("Assertion failed")
  86 +// }
  87 +// }
  88 +//
  89 +// return data
  90 +// }
  91 +
  92 +val ImageProxy.PlaneProxy.size
  93 + get() = buffer.remaining()
  94 +
  95 +val ImageProxy.y: ImageProxy.PlaneProxy
  96 + get() = planes[0]
  97 +
  98 +val ImageProxy.u: ImageProxy.PlaneProxy
  99 + get() = planes[1]
  100 +
  101 +val ImageProxy.v: ImageProxy.PlaneProxy
  102 + get() = planes[2]
  103 +
  104 +val Barcode.data: Map<String, Any?>
  105 + get() = mapOf("corners" to cornerPoints?.map { corner -> corner.data }, "format" to format,
  106 + "rawBytes" to rawBytes, "rawValue" to rawValue, "type" to valueType,
  107 + "calendarEvent" to calendarEvent?.data, "contactInfo" to contactInfo?.data,
  108 + "driverLicense" to driverLicense?.data, "email" to email?.data,
  109 + "geoPoint" to geoPoint?.data, "phone" to phone?.data, "sms" to sms?.data,
  110 + "url" to url?.data, "wifi" to wifi?.data)
  111 +
  112 +val Point.data: Map<String, Double>
  113 + get() = mapOf("x" to x.toDouble(), "y" to y.toDouble())
  114 +
  115 +val Barcode.CalendarEvent.data: Map<String, Any?>
  116 + get() = mapOf("description" to description, "end" to end?.rawValue, "location" to location,
  117 + "organizer" to organizer, "start" to start?.rawValue, "status" to status,
  118 + "summary" to summary)
  119 +
  120 +val Barcode.ContactInfo.data: Map<String, Any?>
  121 + get() = mapOf("addresses" to addresses.map { address -> address.data },
  122 + "emails" to emails.map { email -> email.data }, "name" to name?.data,
  123 + "organization" to organization, "phones" to phones.map { phone -> phone.data },
  124 + "title" to title, "urls" to urls)
  125 +
  126 +val Barcode.Address.data: Map<String, Any?>
  127 + get() = mapOf("addressLines" to addressLines, "type" to type)
  128 +
  129 +val Barcode.PersonName.data: Map<String, Any?>
  130 + get() = mapOf("first" to first, "formattedName" to formattedName, "last" to last,
  131 + "middle" to middle, "prefix" to prefix, "pronunciation" to pronunciation,
  132 + "suffix" to suffix)
  133 +
  134 +val Barcode.DriverLicense.data: Map<String, Any?>
  135 + get() = mapOf("addressCity" to addressCity, "addressState" to addressState,
  136 + "addressStreet" to addressStreet, "addressZip" to addressZip, "birthDate" to birthDate,
  137 + "documentType" to documentType, "expiryDate" to expiryDate, "firstName" to firstName,
  138 + "gender" to gender, "issueDate" to issueDate, "issuingCountry" to issuingCountry,
  139 + "lastName" to lastName, "licenseNumber" to licenseNumber, "middleName" to middleName)
  140 +
  141 +val Barcode.Email.data: Map<String, Any?>
  142 + get() = mapOf("address" to address, "body" to body, "subject" to subject, "type" to type)
  143 +
  144 +val Barcode.GeoPoint.data: Map<String, Any?>
  145 + get() = mapOf("latitude" to lat, "longitude" to lng)
  146 +
  147 +val Barcode.Phone.data: Map<String, Any?>
  148 + get() = mapOf("number" to number, "type" to type)
  149 +
  150 +val Barcode.Sms.data: Map<String, Any?>
  151 + get() = mapOf("message" to message, "phoneNumber" to phoneNumber)
  152 +
  153 +val Barcode.UrlBookmark.data: Map<String, Any?>
  154 + get() = mapOf("title" to title, "url" to url)
  155 +
  156 +val Barcode.WiFi.data: Map<String, Any?>
  157 + get() = mapOf("encryptionType" to encryptionType, "password" to password, "ssid" to ssid)
  1 +package dev.steenbakker.mobile_scanner.exceptions
  2 +
  3 +internal class NoPermissionException : RuntimeException()
  1 +package dev.steenbakker.mobile_scanner.old
  2 +
  3 +import com.google.mlkit.vision.barcode.BarcodeScannerOptions
  4 +import java.util.ArrayList
  5 +
  6 +enum class BarcodeFormats(val intValue: Int) {
  7 + 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(
  8 + com.google.mlkit.vision.barcode.common.Barcode.FORMAT_CODE_39
  9 + ),
  10 + CODE_93(com.google.mlkit.vision.barcode.common.Barcode.FORMAT_CODE_93), CODABAR(com.google.mlkit.vision.barcode.common.Barcode.FORMAT_CODABAR), DATA_MATRIX(
  11 + com.google.mlkit.vision.barcode.common.Barcode.FORMAT_DATA_MATRIX
  12 + ),
  13 + 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(
  14 + com.google.mlkit.vision.barcode.common.Barcode.FORMAT_ITF
  15 + ),
  16 + 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(
  17 + com.google.mlkit.vision.barcode.common.Barcode.FORMAT_UPC_E
  18 + ),
  19 + PDF417(com.google.mlkit.vision.barcode.common.Barcode.FORMAT_PDF417), AZTEC(com.google.mlkit.vision.barcode.common.Barcode.FORMAT_AZTEC);
  20 +
  21 + companion object {
  22 + private var formatsMap: MutableMap<String, Int>? = null
  23 +
  24 + /**
  25 + * Return the integer value resuling from OR-ing all of the values
  26 + * of the supplied strings.
  27 + *
  28 + *
  29 + * Note that if ALL_FORMATS is defined as well as other values, ALL_FORMATS
  30 + * will be ignored (following how it would work with just OR-ing the ints).
  31 + *
  32 + * @param strings - list of strings representing the various formats
  33 + * @return integer value corresponding to OR of all the values.
  34 + */
  35 + fun intFromStringList(strings: List<String>?): Int {
  36 + if (strings == null) return ALL_FORMATS.intValue
  37 + var `val` = 0
  38 + for (string in strings) {
  39 + val asInt = formatsMap!![string]
  40 + if (asInt != null) {
  41 + `val` = `val` or asInt
  42 + }
  43 + }
  44 + return `val`
  45 + }
  46 +
  47 + fun optionsFromStringList(strings: List<String>?): BarcodeScannerOptions {
  48 + if (strings == null) {
  49 + return BarcodeScannerOptions.Builder().setBarcodeFormats(ALL_FORMATS.intValue)
  50 + .build()
  51 + }
  52 + val ints: MutableList<Int> = ArrayList(strings.size)
  53 + run {
  54 + var i = 0
  55 + val l = strings.size
  56 + while (i < l) {
  57 + val integer =
  58 + formatsMap!![strings[i]]
  59 + if (integer != null) {
  60 + ints.add(integer)
  61 + }
  62 + ++i
  63 + }
  64 + }
  65 + if (ints.size == 0) {
  66 + return BarcodeScannerOptions.Builder().setBarcodeFormats(ALL_FORMATS.intValue)
  67 + .build()
  68 + }
  69 + if (ints.size == 1) {
  70 + return BarcodeScannerOptions.Builder().setBarcodeFormats(ints[0]).build()
  71 + }
  72 + val first = ints[0]
  73 + val rest = IntArray(ints.size - 1)
  74 + var i = 0
  75 + for (e in ints.subList(1, ints.size)) {
  76 + rest[i++] = e
  77 + }
  78 + return BarcodeScannerOptions.Builder()
  79 + .setBarcodeFormats(first, *rest).build()
  80 + }
  81 +
  82 + init {
  83 + val values = values()
  84 +// formatsMap =
  85 +// HashMap<String, Int>(com.github.rmtmckenzie.qrmobilevision.values.size * 4 / 3)
  86 +// for (value in com.github.rmtmckenzie.qrmobilevision.values) {
  87 +// formatsMap!![com.github.rmtmckenzie.qrmobilevision.value.name] =
  88 +// com.github.rmtmckenzie.qrmobilevision.value.intValue
  89 +// }
  90 + }
  91 + }
  92 +}
  1 +package dev.steenbakker.mobile_scanner.old
  2 +
  3 +import android.Manifest
  4 +import android.annotation.SuppressLint
  5 +import android.app.Activity
  6 +import android.content.Context
  7 +import android.content.pm.PackageManager
  8 +import android.util.Log
  9 +import android.view.Surface
  10 +import androidx.camera.core.*
  11 +import androidx.camera.lifecycle.ProcessCameraProvider
  12 +import androidx.core.content.ContextCompat
  13 +import androidx.lifecycle.LifecycleOwner
  14 +import com.google.mlkit.vision.barcode.BarcodeScannerOptions
  15 +import com.google.mlkit.vision.barcode.BarcodeScanning
  16 +import com.google.mlkit.vision.common.InputImage
  17 +import dev.steenbakker.mobile_scanner.exceptions.NoPermissionException
  18 +import io.flutter.plugin.common.MethodChannel
  19 +import io.flutter.view.TextureRegistry
  20 +import java.io.IOException
  21 +
  22 +internal class MobileScanner(
  23 + private val context: Activity,
  24 + private val texture: TextureRegistry
  25 +) {
  26 +
  27 + private var cameraProvider: ProcessCameraProvider? = null
  28 + private var camera: Camera? = null
  29 + private var textureEntry: TextureRegistry.SurfaceTextureEntry? = null
  30 +
  31 + @ExperimentalGetImage
  32 + @Throws(IOException::class, NoPermissionException::class, Exception::class)
  33 + fun start(
  34 + result: MethodChannel.Result,
  35 + options: BarcodeScannerOptions?,
  36 + channel: MethodChannel
  37 + ) {
  38 + if (!hasCameraHardware(context)) {
  39 + throw Exception(Exception.Reason.noHardware)
  40 + }
  41 + if (!checkCameraPermission(context)) {
  42 + throw NoPermissionException()
  43 + }
  44 +
  45 + textureEntry = texture.createSurfaceTexture()
  46 + val textureId = textureEntry!!.id()
  47 +
  48 + val future = ProcessCameraProvider.getInstance(context)
  49 + val executor = ContextCompat.getMainExecutor(context)
  50 + future.addListener({
  51 +
  52 + // Preview
  53 + val surfaceProvider = Preview.SurfaceProvider { request ->
  54 + val resolution = request.resolution
  55 + val texture = textureEntry!!.surfaceTexture()
  56 + texture.setDefaultBufferSize(resolution.width, resolution.height)
  57 + val surface = Surface(texture)
  58 + request.provideSurface(surface, executor, { })
  59 + }
  60 +
  61 + val preview = Preview.Builder().build().apply { setSurfaceProvider(surfaceProvider) }
  62 + val analyzer = ImageAnalysis.Analyzer { imageProxy -> // YUV_420_888 format
  63 + val mediaImage = imageProxy.image ?: return@Analyzer
  64 + val inputImage =
  65 + InputImage.fromMediaImage(mediaImage, imageProxy.imageInfo.rotationDegrees)
  66 + val scanner = if (options != null) {
  67 + BarcodeScanning.getClient(options)
  68 + } else {
  69 + BarcodeScanning.getClient()
  70 + }
  71 +
  72 + scanner.process(inputImage)
  73 + .addOnSuccessListener { barcodes ->
  74 + val barcodeList: MutableList<Map<String, Any?>> = mutableListOf()
  75 + for (barcode in barcodes) {
  76 + barcodeList.add(
  77 + mapOf(
  78 + "value" to barcode.rawValue,
  79 + "bytes" to barcode.rawBytes
  80 + )
  81 + )
  82 +
  83 + }
  84 + channel.invokeMethod("qrRead", barcodeList)
  85 + }
  86 + .addOnFailureListener { e -> Log.e("Camera", e.message, e) }
  87 + .addOnCompleteListener { imageProxy.close() }
  88 + }
  89 + val analysis = ImageAnalysis.Builder()
  90 + .setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
  91 + .build().apply { setAnalyzer(executor, analyzer) }
  92 + // Bind to lifecycle.
  93 + val owner = context as LifecycleOwner
  94 +// val selector =
  95 +// if (call.arguments == 0) CameraSelector.DEFAULT_FRONT_CAMERA
  96 +// else CameraSelector.DEFAULT_BACK_CAMERA
  97 + camera = cameraProvider!!.bindToLifecycle(
  98 + owner,
  99 + CameraSelector.DEFAULT_BACK_CAMERA,
  100 + preview,
  101 + analysis
  102 + )
  103 + camera!!.cameraInfo.torchState.observe(owner, { state ->
  104 + // TorchState.OFF = 0; TorchState.ON = 1
  105 +// val event = mapOf("name" to "torchState", "data" to state)
  106 +// sink?.success(event)
  107 + })
  108 + // TODO: seems there's not a better way to get the final resolution
  109 + @SuppressLint("RestrictedApi")
  110 + val resolution = preview.attachedSurfaceResolution!!
  111 + val portrait = camera!!.cameraInfo.sensorRotationDegrees % 180 == 0
  112 + val width = resolution.width.toDouble()
  113 + val height = resolution.height.toDouble()
  114 + val size = if (portrait) mapOf(
  115 + "width" to width,
  116 + "height" to height
  117 + ) else mapOf("width" to height, "height" to width)
  118 + result.success(mapOf("textureId" to textureId, "size" to size))
  119 + }, executor)
  120 + }
  121 +
  122 + fun stop() {
  123 + val owner = context as LifecycleOwner
  124 + camera!!.cameraInfo.torchState.removeObservers(owner)
  125 + cameraProvider!!.unbindAll()
  126 + textureEntry!!.release()
  127 +
  128 + camera = null
  129 + textureEntry = null
  130 + cameraProvider = null
  131 + }
  132 +
  133 + private fun hasCameraHardware(context: Context): Boolean {
  134 + return context.packageManager.hasSystemFeature(PackageManager.FEATURE_CAMERA_ANY)
  135 + }
  136 +
  137 + private fun checkCameraPermission(context: Context): Boolean {
  138 + val permissions = arrayOf(Manifest.permission.CAMERA)
  139 + val res = context.checkCallingOrSelfPermission(permissions[0])
  140 + return res == PackageManager.PERMISSION_GRANTED
  141 + }
  142 +
  143 + internal class Exception(val reason: Reason) :
  144 + java.lang.Exception("Mobile Scanner failed because $reason") {
  145 +
  146 + internal enum class Reason {
  147 + noHardware, noPermissions, noBackCamera
  148 + }
  149 + }
  150 +}
  1 +package dev.steenbakker.mobile_scanner.old
  2 +
  3 +import android.Manifest
  4 +import android.app.Activity
  5 +import android.content.pm.PackageManager
  6 +import android.util.Log
  7 +import androidx.annotation.NonNull
  8 +import androidx.camera.core.ExperimentalGetImage
  9 +import androidx.core.app.ActivityCompat
  10 +import dev.steenbakker.mobile_scanner.exceptions.NoPermissionException
  11 +
  12 +import io.flutter.embedding.engine.plugins.FlutterPlugin
  13 +import io.flutter.embedding.engine.plugins.activity.ActivityAware
  14 +import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding
  15 +import io.flutter.plugin.common.EventChannel
  16 +import io.flutter.plugin.common.MethodCall
  17 +import io.flutter.plugin.common.MethodChannel
  18 +import io.flutter.plugin.common.MethodChannel.MethodCallHandler
  19 +import io.flutter.plugin.common.MethodChannel.Result
  20 +import io.flutter.plugin.common.PluginRegistry.RequestPermissionsResultListener
  21 +import io.flutter.view.TextureRegistry
  22 +import java.io.IOException
  23 +
  24 +/** MobileScannerPlugin */
  25 +class MobileScannerPlugin: FlutterPlugin, MethodCallHandler, ActivityAware,
  26 + RequestPermissionsResultListener, EventChannel.StreamHandler {
  27 +
  28 + private lateinit var textures: TextureRegistry
  29 + private lateinit var channel : MethodChannel
  30 + private lateinit var event : EventChannel
  31 + private var activity: Activity? = null
  32 + private var waitingForPermissionResult = false
  33 + private var sink: EventChannel.EventSink? = null
  34 +
  35 + private var mobileScanner: MobileScanner? = null
  36 +
  37 + override fun onAttachedToActivity(binding: ActivityPluginBinding) {
  38 + activity = binding.activity
  39 + }
  40 +
  41 + override fun onDetachedFromActivityForConfigChanges() {
  42 + onDetachedFromActivity()
  43 + }
  44 +
  45 + override fun onReattachedToActivityForConfigChanges(binding: ActivityPluginBinding) {
  46 + onAttachedToActivity(binding)
  47 + }
  48 +
  49 + override fun onDetachedFromActivity() {
  50 + activity = null
  51 + }
  52 +
  53 +
  54 + override fun onDetachedFromEngine(@NonNull binding: FlutterPlugin.FlutterPluginBinding) {
  55 + channel.setMethodCallHandler(null)
  56 + }
  57 +
  58 +
  59 + override fun onAttachedToEngine(@NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) {
  60 + textures = flutterPluginBinding.textureRegistry
  61 + channel = MethodChannel(flutterPluginBinding.binaryMessenger, "dev.steenbakker.mobile_scanner/scanner/method")
  62 + event = EventChannel(flutterPluginBinding.binaryMessenger, "dev.steenbakker.mobile_scanner/scanner/event")
  63 + channel.setMethodCallHandler(this)
  64 + event.setStreamHandler(this)
  65 + }
  66 +
  67 + override fun onListen(arguments: Any?, events: EventChannel.EventSink?) {
  68 + this.sink = events
  69 + }
  70 +
  71 + override fun onCancel(arguments: Any?) {
  72 + sink = null
  73 + }
  74 +
  75 +
  76 + override fun onRequestPermissionsResult(
  77 + requestCode: Int,
  78 + permissions: Array<String?>?,
  79 + grantResults: IntArray
  80 + ): Boolean {
  81 + if (requestCode == 105505) {
  82 + waitingForPermissionResult = false
  83 + if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
  84 + Log.i(
  85 + "mobile_scanner",
  86 + "Permissions request granted."
  87 + )
  88 + mobileScanner?.stop()
  89 + } else {
  90 + Log.i(
  91 + "mobile_scanner",
  92 + "Permissions request denied."
  93 + )
  94 + mobileScanner?.stop()
  95 + }
  96 + return true
  97 + }
  98 + return false
  99 + }
  100 +
  101 + @ExperimentalGetImage
  102 + override fun onMethodCall(@NonNull call: MethodCall, @NonNull result: Result) {
  103 + when (call.method) {
  104 + "start" -> {
  105 +// val targetWidth: Int? = call.argument<Int>("targetWidth")
  106 +// val targetHeight: Int? = call.argument<Int>("targetHeight")
  107 +// val formatStrings: List<String>? = call.argument<List<String>>("formats")
  108 +// if (targetWidth == null || targetHeight == null) {
  109 +// result.error(
  110 +// "INVALID_ARGUMENT",
  111 +// "Missing a required argument",
  112 +// "Expecting targetWidth, targetHeight"
  113 +// )
  114 +// return
  115 +// }
  116 +
  117 +// val options: BarcodeScannerOptions = BarcodeFormats.optionsFromStringList(formatStrings)
  118 +
  119 +// mobileScanner ?:
  120 +
  121 + try {
  122 + MobileScanner(activity!!, textures).start(result, null, channel)
  123 + } catch (e: IOException) {
  124 + e.printStackTrace()
  125 + result.error(
  126 + "IOException",
  127 + "Error starting camera because of IOException: " + e.localizedMessage,
  128 + null
  129 + )
  130 + } catch (e: MobileScanner.Exception) {
  131 + e.printStackTrace()
  132 + result.error(
  133 + e.reason.name,
  134 + "Error starting camera for reason: " + e.reason.name,
  135 + null
  136 + )
  137 + } catch (e: NoPermissionException) {
  138 + waitingForPermissionResult = true
  139 + ActivityCompat.requestPermissions(
  140 + activity!!,
  141 + arrayOf(Manifest.permission.CAMERA),
  142 + 105505
  143 + )
  144 + }
  145 + }
  146 + "stop" -> {
  147 + if (mobileScanner != null && !waitingForPermissionResult) {
  148 + mobileScanner!!.stop()
  149 + }
  150 + result.success(null)
  151 + }
  152 + else -> result.notImplemented()
  153 + }
  154 + }
  155 +
  156 +}
@@ -24,6 +24,8 @@ if (flutterVersionName == null) { @@ -24,6 +24,8 @@ if (flutterVersionName == null) {
24 apply plugin: 'com.android.application' 24 apply plugin: 'com.android.application'
25 apply plugin: 'kotlin-android' 25 apply plugin: 'kotlin-android'
26 apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" 26 apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
  27 +// Apply the Performance Monitoring plugin
  28 +apply plugin: 'com.google.firebase.firebase-perf'
27 29
28 android { 30 android {
29 compileSdkVersion flutter.compileSdkVersion 31 compileSdkVersion flutter.compileSdkVersion
@@ -44,7 +46,7 @@ android { @@ -44,7 +46,7 @@ android {
44 defaultConfig { 46 defaultConfig {
45 // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). 47 // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
46 applicationId "dev.steenbakker.mobile_scanner_example" 48 applicationId "dev.steenbakker.mobile_scanner_example"
47 - minSdkVersion flutter.minSdkVersion 49 + minSdkVersion 21
48 targetSdkVersion flutter.targetSdkVersion 50 targetSdkVersion flutter.targetSdkVersion
49 versionCode flutterVersionCode.toInteger() 51 versionCode flutterVersionCode.toInteger()
50 versionName flutterVersionName 52 versionName flutterVersionName
@@ -6,7 +6,8 @@ buildscript { @@ -6,7 +6,8 @@ buildscript {
6 } 6 }
7 7
8 dependencies { 8 dependencies {
9 - classpath 'com.android.tools.build:gradle:4.1.0' 9 + classpath 'com.google.firebase:perf-plugin:1.4.0' // Performanc
  10 + classpath 'com.android.tools.build:gradle:7.0.4'
10 classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 11 classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
11 } 12 }
12 } 13 }
1 -#Fri Jun 23 08:50:38 CEST 2017 1 +#Tue Feb 08 10:35:11 CET 2022
2 distributionBase=GRADLE_USER_HOME 2 distributionBase=GRADLE_USER_HOME
  3 +distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-bin.zip
3 distributionPath=wrapper/dists 4 distributionPath=wrapper/dists
4 -zipStoreBase=GRADLE_USER_HOME  
5 zipStorePath=wrapper/dists 5 zipStorePath=wrapper/dists
6 -distributionUrl=https\://services.gradle.org/distributions/gradle-6.7-all.zip 6 +zipStoreBase=GRADLE_USER_HOME
1 # Uncomment this line to define a global platform for your project 1 # Uncomment this line to define a global platform for your project
2 -# platform :ios, '9.0' 2 +platform :ios, '10.0'
3 3
4 # CocoaPods analytics sends network stats synchronously affecting flutter build latency. 4 # CocoaPods analytics sends network stats synchronously affecting flutter build latency.
5 ENV['COCOAPODS_DISABLE_STATS'] = 'true' 5 ENV['COCOAPODS_DISABLE_STATS'] = 'true'
@@ -3,7 +3,7 @@ @@ -3,7 +3,7 @@
3 archiveVersion = 1; 3 archiveVersion = 1;
4 classes = { 4 classes = {
5 }; 5 };
6 - objectVersion = 50; 6 + objectVersion = 51;
7 objects = { 7 objects = {
8 8
9 /* Begin PBXBuildFile section */ 9 /* Begin PBXBuildFile section */
@@ -13,6 +13,7 @@ @@ -13,6 +13,7 @@
13 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 13 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
14 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 14 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
15 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; 15 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
  16 + C80F46710D9B9F4F17AD4E3D /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5E133769572782C32D37D8AC /* Pods_Runner.framework */; };
16 /* End PBXBuildFile section */ 17 /* End PBXBuildFile section */
17 18
18 /* Begin PBXCopyFilesBuildPhase section */ 19 /* Begin PBXCopyFilesBuildPhase section */
@@ -31,7 +32,9 @@ @@ -31,7 +32,9 @@
31 /* Begin PBXFileReference section */ 32 /* Begin PBXFileReference section */
32 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = "<group>"; }; 33 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = "<group>"; };
33 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = "<group>"; }; 34 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = "<group>"; };
  35 + 1CD9C88F6BFEF6CB7CA6746B /* 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>"; };
34 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = "<group>"; }; 36 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = "<group>"; };
  37 + 5E133769572782C32D37D8AC /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; };
35 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = "<group>"; }; 38 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = "<group>"; };
36 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; }; 39 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
37 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = "<group>"; }; 40 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = "<group>"; };
@@ -42,6 +45,8 @@ @@ -42,6 +45,8 @@
42 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; }; 45 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
43 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; }; 46 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
44 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; }; 47 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
  48 + E29A089CD1D61281C49DBB79 /* 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>"; };
  49 + E33BE6AC5C06F7A45470ADE0 /* 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>"; };
45 /* End PBXFileReference section */ 50 /* End PBXFileReference section */
46 51
47 /* Begin PBXFrameworksBuildPhase section */ 52 /* Begin PBXFrameworksBuildPhase section */
@@ -49,12 +54,31 @@ @@ -49,12 +54,31 @@
49 isa = PBXFrameworksBuildPhase; 54 isa = PBXFrameworksBuildPhase;
50 buildActionMask = 2147483647; 55 buildActionMask = 2147483647;
51 files = ( 56 files = (
  57 + C80F46710D9B9F4F17AD4E3D /* Pods_Runner.framework in Frameworks */,
52 ); 58 );
53 runOnlyForDeploymentPostprocessing = 0; 59 runOnlyForDeploymentPostprocessing = 0;
54 }; 60 };
55 /* End PBXFrameworksBuildPhase section */ 61 /* End PBXFrameworksBuildPhase section */
56 62
57 /* Begin PBXGroup section */ 63 /* Begin PBXGroup section */
  64 + 0F766276E0F46921DEBF581B /* Frameworks */ = {
  65 + isa = PBXGroup;
  66 + children = (
  67 + 5E133769572782C32D37D8AC /* Pods_Runner.framework */,
  68 + );
  69 + name = Frameworks;
  70 + sourceTree = "<group>";
  71 + };
  72 + 203D5C95A734778D93D18369 /* Pods */ = {
  73 + isa = PBXGroup;
  74 + children = (
  75 + E33BE6AC5C06F7A45470ADE0 /* Pods-Runner.debug.xcconfig */,
  76 + 1CD9C88F6BFEF6CB7CA6746B /* Pods-Runner.release.xcconfig */,
  77 + E29A089CD1D61281C49DBB79 /* Pods-Runner.profile.xcconfig */,
  78 + );
  79 + path = Pods;
  80 + sourceTree = "<group>";
  81 + };
58 9740EEB11CF90186004384FC /* Flutter */ = { 82 9740EEB11CF90186004384FC /* Flutter */ = {
59 isa = PBXGroup; 83 isa = PBXGroup;
60 children = ( 84 children = (
@@ -72,6 +96,8 @@ @@ -72,6 +96,8 @@
72 9740EEB11CF90186004384FC /* Flutter */, 96 9740EEB11CF90186004384FC /* Flutter */,
73 97C146F01CF9000F007C117D /* Runner */, 97 97C146F01CF9000F007C117D /* Runner */,
74 97C146EF1CF9000F007C117D /* Products */, 98 97C146EF1CF9000F007C117D /* Products */,
  99 + 203D5C95A734778D93D18369 /* Pods */,
  100 + 0F766276E0F46921DEBF581B /* Frameworks */,
75 ); 101 );
76 sourceTree = "<group>"; 102 sourceTree = "<group>";
77 }; 103 };
@@ -105,12 +131,14 @@ @@ -105,12 +131,14 @@
105 isa = PBXNativeTarget; 131 isa = PBXNativeTarget;
106 buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; 132 buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
107 buildPhases = ( 133 buildPhases = (
  134 + 1C759CA63421B131D22BB688 /* [CP] Check Pods Manifest.lock */,
108 9740EEB61CF901F6004384FC /* Run Script */, 135 9740EEB61CF901F6004384FC /* Run Script */,
109 97C146EA1CF9000F007C117D /* Sources */, 136 97C146EA1CF9000F007C117D /* Sources */,
110 97C146EB1CF9000F007C117D /* Frameworks */, 137 97C146EB1CF9000F007C117D /* Frameworks */,
111 97C146EC1CF9000F007C117D /* Resources */, 138 97C146EC1CF9000F007C117D /* Resources */,
112 9705A1C41CF9048500538489 /* Embed Frameworks */, 139 9705A1C41CF9048500538489 /* Embed Frameworks */,
113 3B06AD1E1E4923F5004D2608 /* Thin Binary */, 140 3B06AD1E1E4923F5004D2608 /* Thin Binary */,
  141 + EE97B31B239E017B5516C6AD /* [CP] Embed Pods Frameworks */,
114 ); 142 );
115 buildRules = ( 143 buildRules = (
116 ); 144 );
@@ -169,6 +197,28 @@ @@ -169,6 +197,28 @@
169 /* End PBXResourcesBuildPhase section */ 197 /* End PBXResourcesBuildPhase section */
170 198
171 /* Begin PBXShellScriptBuildPhase section */ 199 /* Begin PBXShellScriptBuildPhase section */
  200 + 1C759CA63421B131D22BB688 /* [CP] Check Pods Manifest.lock */ = {
  201 + isa = PBXShellScriptBuildPhase;
  202 + buildActionMask = 2147483647;
  203 + files = (
  204 + );
  205 + inputFileListPaths = (
  206 + );
  207 + inputPaths = (
  208 + "${PODS_PODFILE_DIR_PATH}/Podfile.lock",
  209 + "${PODS_ROOT}/Manifest.lock",
  210 + );
  211 + name = "[CP] Check Pods Manifest.lock";
  212 + outputFileListPaths = (
  213 + );
  214 + outputPaths = (
  215 + "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt",
  216 + );
  217 + runOnlyForDeploymentPostprocessing = 0;
  218 + shellPath = /bin/sh;
  219 + 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";
  220 + showEnvVarsInLog = 0;
  221 + };
172 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { 222 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = {
173 isa = PBXShellScriptBuildPhase; 223 isa = PBXShellScriptBuildPhase;
174 buildActionMask = 2147483647; 224 buildActionMask = 2147483647;
@@ -197,6 +247,23 @@ @@ -197,6 +247,23 @@
197 shellPath = /bin/sh; 247 shellPath = /bin/sh;
198 shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; 248 shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build";
199 }; 249 };
  250 + EE97B31B239E017B5516C6AD /* [CP] Embed Pods Frameworks */ = {
  251 + isa = PBXShellScriptBuildPhase;
  252 + buildActionMask = 2147483647;
  253 + files = (
  254 + );
  255 + inputFileListPaths = (
  256 + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist",
  257 + );
  258 + name = "[CP] Embed Pods Frameworks";
  259 + outputFileListPaths = (
  260 + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist",
  261 + );
  262 + runOnlyForDeploymentPostprocessing = 0;
  263 + shellPath = /bin/sh;
  264 + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n";
  265 + showEnvVarsInLog = 0;
  266 + };
200 /* End PBXShellScriptBuildPhase section */ 267 /* End PBXShellScriptBuildPhase section */
201 268
202 /* Begin PBXSourcesBuildPhase section */ 269 /* Begin PBXSourcesBuildPhase section */
@@ -288,7 +355,7 @@ @@ -288,7 +355,7 @@
288 ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 355 ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
289 CLANG_ENABLE_MODULES = YES; 356 CLANG_ENABLE_MODULES = YES;
290 CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; 357 CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
291 - DEVELOPMENT_TEAM = 3K8Q7WKS3W; 358 + DEVELOPMENT_TEAM = 75Y2P2WSQQ;
292 ENABLE_BITCODE = NO; 359 ENABLE_BITCODE = NO;
293 INFOPLIST_FILE = Runner/Info.plist; 360 INFOPLIST_FILE = Runner/Info.plist;
294 LD_RUNPATH_SEARCH_PATHS = ( 361 LD_RUNPATH_SEARCH_PATHS = (
@@ -417,7 +484,7 @@ @@ -417,7 +484,7 @@
417 ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 484 ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
418 CLANG_ENABLE_MODULES = YES; 485 CLANG_ENABLE_MODULES = YES;
419 CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; 486 CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
420 - DEVELOPMENT_TEAM = 3K8Q7WKS3W; 487 + DEVELOPMENT_TEAM = 75Y2P2WSQQ;
421 ENABLE_BITCODE = NO; 488 ENABLE_BITCODE = NO;
422 INFOPLIST_FILE = Runner/Info.plist; 489 INFOPLIST_FILE = Runner/Info.plist;
423 LD_RUNPATH_SEARCH_PATHS = ( 490 LD_RUNPATH_SEARCH_PATHS = (
@@ -440,7 +507,7 @@ @@ -440,7 +507,7 @@
440 ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 507 ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
441 CLANG_ENABLE_MODULES = YES; 508 CLANG_ENABLE_MODULES = YES;
442 CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; 509 CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
443 - DEVELOPMENT_TEAM = 3K8Q7WKS3W; 510 + DEVELOPMENT_TEAM = 75Y2P2WSQQ;
444 ENABLE_BITCODE = NO; 511 ENABLE_BITCODE = NO;
445 INFOPLIST_FILE = Runner/Info.plist; 512 INFOPLIST_FILE = Runner/Info.plist;
446 LD_RUNPATH_SEARCH_PATHS = ( 513 LD_RUNPATH_SEARCH_PATHS = (
@@ -4,4 +4,7 @@ @@ -4,4 +4,7 @@
4 <FileRef 4 <FileRef
5 location = "group:Runner.xcodeproj"> 5 location = "group:Runner.xcodeproj">
6 </FileRef> 6 </FileRef>
  7 + <FileRef
  8 + location = "group:Pods/Pods.xcodeproj">
  9 + </FileRef>
7 </Workspace> 10 </Workspace>
@@ -2,6 +2,8 @@ @@ -2,6 +2,8 @@
2 <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> 2 <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3 <plist version="1.0"> 3 <plist version="1.0">
4 <dict> 4 <dict>
  5 + <key>NSCameraUsageDescription</key>
  6 + <string>We use the camera to scan barcodes</string>
5 <key>CFBundleDevelopmentRegion</key> 7 <key>CFBundleDevelopmentRegion</key>
6 <string>$(DEVELOPMENT_LANGUAGE)</string> 8 <string>$(DEVELOPMENT_LANGUAGE)</string>
7 <key>CFBundleDisplayName</key> 9 <key>CFBundleDisplayName</key>
1 -import 'package:flutter/material.dart';  
2 -import 'dart:async';  
3 1
4 -import 'package:flutter/services.dart'; 2 +import 'dart:ui';
  3 +
  4 +import 'package:flutter/material.dart';
5 import 'package:mobile_scanner/mobile_scanner.dart'; 5 import 'package:mobile_scanner/mobile_scanner.dart';
6 6
7 void main() { 7 void main() {
8 - runApp(const MyApp()); 8 + runApp(const AnalyzeView());
9 } 9 }
10 10
11 -class MyApp extends StatefulWidget {  
12 - const MyApp({Key? key}) : super(key: key); 11 +class AnalyzeView extends StatefulWidget {
  12 + const AnalyzeView({Key? key}) : super(key: key);
13 13
14 @override 14 @override
15 - State<MyApp> createState() => _MyAppState(); 15 + _AnalyzeViewState createState() => _AnalyzeViewState();
16 } 16 }
17 17
18 -class _MyAppState extends State<MyApp> {  
19 - String _platformVersion = 'Unknown'; 18 +class _AnalyzeViewState extends State<AnalyzeView>
  19 + with SingleTickerProviderStateMixin {
20 20
21 - @override  
22 - void initState() {  
23 - super.initState();  
24 - initPlatformState();  
25 - } 21 + List<Offset> points = [];
26 22
27 - // Platform messages are asynchronous, so we initialize in an async method.  
28 - Future<void> initPlatformState() async {  
29 - String platformVersion;  
30 - // Platform messages may fail, so we use a try/catch PlatformException.  
31 - // We also handle the message potentially returning null.  
32 - try {  
33 - platformVersion =  
34 - await MobileScanner.platformVersion ?? 'Unknown platform version';  
35 - } on PlatformException {  
36 - platformVersion = 'Failed to get platform version.';  
37 - } 23 + CameraController cameraController = CameraController();
38 24
39 - // If the widget was removed from the tree while the asynchronous platform  
40 - // message was in flight, we want to discard the reply rather than calling  
41 - // setState to update our non-existent appearance.  
42 - if (!mounted) return; 25 + String? barcode = null;
43 26
44 - setState(() {  
45 - _platformVersion = platformVersion;  
46 - });  
47 - }  
48 27
49 @override 28 @override
50 Widget build(BuildContext context) { 29 Widget build(BuildContext context) {
51 return MaterialApp( 30 return MaterialApp(
52 home: Scaffold( 31 home: Scaffold(
53 - appBar: AppBar(  
54 - title: const Text('Plugin example app'),  
55 - ),  
56 - body: Center(  
57 - child: Text('Running on: $_platformVersion\n'), 32 + body: Builder(
  33 + builder: (context) {
  34 + return Stack(
  35 + children: [
  36 + CameraView(cameraController,
  37 + onDetect: (barcode, args) {
  38 + if (this.barcode != barcode.rawValue) {
  39 + this.barcode = barcode.rawValue;
  40 + if ( barcode.corners != null) {
  41 + debugPrint('Size: ${MediaQuery.of(context).size}');
  42 + ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text('${barcode.rawValue}'), duration: Duration(milliseconds: 200), animation: null,));
  43 + setState(() {
  44 + final List<Offset> points = [];
  45 + double factorWidth = args.size.width / 520;
  46 + double factorHeight = args.size.height / 640;
  47 + for (var point in barcode.corners!) {
  48 + points.add(Offset(point.dx * factorWidth, point.dy * factorHeight));
  49 + }
  50 + this.points = points;
  51 + });
  52 + }
  53 + }
  54 + // Default 640 x480
  55 +
  56 + }),
  57 + Container(
  58 + // width: 400,
  59 + // height: 400,
  60 + child: CustomPaint(
  61 + painter: OpenPainter(points),
  62 + ),
  63 + ),
  64 + Container(
  65 + alignment: Alignment.bottomCenter,
  66 + margin: EdgeInsets.only(bottom: 80.0),
  67 + child: IconButton(
  68 + icon: ValueListenableBuilder(
  69 + valueListenable: cameraController.torchState,
  70 + builder: (context, state, child) {
  71 + final color =
  72 + state == TorchState.off ? Colors.grey : Colors.white;
  73 + return Icon(Icons.bolt, color: color);
  74 + },
  75 + ),
  76 + iconSize: 32.0,
  77 + onPressed: () => cameraController.torch(),
  78 + ),
  79 + ),
  80 + ],
  81 + );
  82 + }
58 ), 83 ),
59 ), 84 ),
60 ); 85 );
61 } 86 }
  87 +
  88 +
  89 +
  90 + @override
  91 + void dispose() {
  92 + cameraController.dispose();
  93 + super.dispose();
  94 + }
  95 +
  96 + void display(Barcode barcode) {
  97 + Navigator.of(context).popAndPushNamed('display', arguments: barcode);
  98 + }
  99 +}
  100 +
  101 +class OpenPainter extends CustomPainter {
  102 + final List<Offset> points;
  103 +
  104 + OpenPainter(this.points);
  105 + @override
  106 + void paint(Canvas canvas, Size size) {
  107 + var paint1 = Paint()
  108 + ..color = Color(0xff63aa65)
  109 + ..strokeWidth = 10;
  110 + //draw points on canvas
  111 + canvas.drawPoints(PointMode.points, points, paint1);
  112 + }
  113 +
  114 + @override
  115 + bool shouldRepaint(CustomPainter oldDelegate) => true;
62 } 116 }
  117 +
  118 +class OpacityCurve extends Curve {
  119 + @override
  120 + double transform(double t) {
  121 + if (t < 0.1) {
  122 + return t * 10;
  123 + } else if (t <= 0.9) {
  124 + return 1.0;
  125 + } else {
  126 + return (1.0 - t) * 10;
  127 + }
  128 + }
  129 +}
  130 +
  131 +// import 'package:flutter/material.dart';
  132 +// import 'package:flutter/rendering.dart';
  133 +// import 'package:mobile_scanner/mobile_scanner.dart';
  134 +//
  135 +// void main() {
  136 +// debugPaintSizeEnabled = false;
  137 +// runApp(HomePage());
  138 +// }
  139 +//
  140 +// class HomePage extends StatefulWidget {
  141 +// @override
  142 +// HomeState createState() => HomeState();
  143 +// }
  144 +//
  145 +// class HomeState extends State<HomePage> {
  146 +// @override
  147 +// Widget build(BuildContext context) {
  148 +// return MaterialApp(home: MyApp());
  149 +// }
  150 +// }
  151 +//
  152 +// class MyApp extends StatefulWidget {
  153 +// @override
  154 +// _MyAppState createState() => _MyAppState();
  155 +// }
  156 +//
  157 +// class _MyAppState extends State<MyApp> {
  158 +// String? qr;
  159 +// bool camState = false;
  160 +//
  161 +// @override
  162 +// initState() {
  163 +// super.initState();
  164 +// }
  165 +//
  166 +// @override
  167 +// Widget build(BuildContext context) {
  168 +// return Scaffold(
  169 +// appBar: AppBar(
  170 +// title: Text('Plugin example app'),
  171 +// ),
  172 +// body: Center(
  173 +// child: Column(
  174 +// crossAxisAlignment: CrossAxisAlignment.center,
  175 +// mainAxisAlignment: MainAxisAlignment.center,
  176 +// children: <Widget>[
  177 +// Expanded(
  178 +// child: camState
  179 +// ? Center(
  180 +// child: SizedBox(
  181 +// width: 300.0,
  182 +// height: 600.0,
  183 +// child: MobileScanner(
  184 +// onError: (context, error) => Text(
  185 +// error.toString(),
  186 +// style: TextStyle(color: Colors.red),
  187 +// ),
  188 +// qrCodeCallback: (code) {
  189 +// setState(() {
  190 +// qr = code;
  191 +// });
  192 +// },
  193 +// child: Container(
  194 +// decoration: BoxDecoration(
  195 +// color: Colors.transparent,
  196 +// border: Border.all(
  197 +// color: Colors.orange,
  198 +// width: 10.0,
  199 +// style: BorderStyle.solid),
  200 +// ),
  201 +// ),
  202 +// ),
  203 +// ),
  204 +// )
  205 +// : Center(child: Text("Camera inactive"))),
  206 +// Text("QRCODE: $qr"),
  207 +// ],
  208 +// ),
  209 +// ),
  210 +// floatingActionButton: FloatingActionButton(
  211 +// child: Text(
  212 +// "press me",
  213 +// textAlign: TextAlign.center,
  214 +// ),
  215 +// onPressed: () {
  216 +// setState(() {
  217 +// camState = !camState;
  218 +// });
  219 +// }),
  220 +// );
  221 +// }
  222 +// }
@@ -13,15 +13,5 @@ import 'package:mobile_scanner_example/main.dart'; @@ -13,15 +13,5 @@ import 'package:mobile_scanner_example/main.dart';
13 void main() { 13 void main() {
14 testWidgets('Verify Platform version', (WidgetTester tester) async { 14 testWidgets('Verify Platform version', (WidgetTester tester) async {
15 // Build our app and trigger a frame. 15 // Build our app and trigger a frame.
16 - await tester.pumpWidget(const MyApp());  
17 -  
18 - // Verify that platform version is retrieved.  
19 - expect(  
20 - find.byWidgetPredicate(  
21 - (Widget widget) => widget is Text &&  
22 - widget.data!.startsWith('Running on:'),  
23 - ),  
24 - findsOneWidget,  
25 - );  
26 }); 16 });
27 } 17 }
  1 +import AVFoundation
1 import Flutter 2 import Flutter
2 -import UIKit 3 +import MLKitVision
  4 +import MLKitBarcodeScanning
3 5
4 -public class SwiftMobileScannerPlugin: NSObject, FlutterPlugin {  
5 - public static func register(with registrar: FlutterPluginRegistrar) {  
6 - let channel = FlutterMethodChannel(name: "mobile_scanner", binaryMessenger: registrar.messenger())  
7 - let instance = SwiftMobileScannerPlugin()  
8 - registrar.addMethodCallDelegate(instance, channel: channel)  
9 - }  
10 -  
11 - public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {  
12 - result("iOS " + UIDevice.current.systemVersion)  
13 - } 6 +public class SwiftMobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHandler, FlutterTexture, AVCaptureVideoDataOutputSampleBufferDelegate {
  7 +
  8 + public static func register(with registrar: FlutterPluginRegistrar) {
  9 + let instance = SwiftMobileScannerPlugin(registrar.textures())
  10 +
  11 + let method = FlutterMethodChannel(name: "dev.steenbakker.mobile_scanner/scanner/method", binaryMessenger: registrar.messenger())
  12 + registrar.addMethodCallDelegate(instance, channel: method)
  13 +
  14 + let event = FlutterEventChannel(name: "dev.steenbakker.mobile_scanner/scanner/event", binaryMessenger: registrar.messenger())
  15 + event.setStreamHandler(instance)
  16 + }
  17 +
  18 + let registry: FlutterTextureRegistry
  19 + var sink: FlutterEventSink!
  20 + var textureId: Int64!
  21 + var captureSession: AVCaptureSession!
  22 + var device: AVCaptureDevice!
  23 + var latestBuffer: CVImageBuffer!
  24 + var analyzeMode: Int
  25 + var analyzing: Bool
  26 +
  27 + init(_ registry: FlutterTextureRegistry) {
  28 + self.registry = registry
  29 + analyzeMode = 0
  30 + analyzing = false
  31 + super.init()
  32 + }
  33 +
  34 + public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
  35 + switch call.method {
  36 + case "state":
  37 + stateNative(call, result)
  38 + case "request":
  39 + requestNative(call, result)
  40 + case "start":
  41 + startNative(call, result)
  42 + case "torch":
  43 + torchNative(call, result)
  44 + case "analyze":
  45 + analyzeNative(call, result)
  46 + case "stop":
  47 + stopNative(result)
  48 + default:
  49 + result(FlutterMethodNotImplemented)
  50 + }
  51 + }
  52 +
  53 + public func onListen(withArguments arguments: Any?, eventSink events: @escaping FlutterEventSink) -> FlutterError? {
  54 + sink = events
  55 + return nil
  56 + }
  57 +
  58 + public func onCancel(withArguments arguments: Any?) -> FlutterError? {
  59 + sink = nil
  60 + return nil
  61 + }
  62 +
  63 + public func copyPixelBuffer() -> Unmanaged<CVPixelBuffer>? {
  64 + if latestBuffer == nil {
  65 + return nil
  66 + }
  67 + return Unmanaged<CVPixelBuffer>.passRetained(latestBuffer)
  68 + }
  69 +
  70 + public func captureOutput(_ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) {
  71 +
  72 + latestBuffer = CMSampleBufferGetImageBuffer(sampleBuffer)
  73 + registry.textureFrameAvailable(textureId)
  74 +
  75 + switch analyzeMode {
  76 + case 1: // barcode
  77 + if analyzing {
  78 + break
  79 + }
  80 + analyzing = true
  81 + let buffer = CMSampleBufferGetImageBuffer(sampleBuffer)
  82 + let image = VisionImage(image: buffer!.image)
  83 + let scanner = BarcodeScanner.barcodeScanner()
  84 + scanner.process(image) { [self] barcodes, error in
  85 + if error == nil && barcodes != nil {
  86 + for barcode in barcodes! {
  87 + let event: [String: Any?] = ["name": "barcode", "data": barcode.data]
  88 + sink?(event)
  89 + }
  90 + }
  91 + analyzing = false
  92 + }
  93 + default: // none
  94 + break
  95 + }
  96 + }
  97 +
  98 + func stateNative(_ call: FlutterMethodCall, _ result: @escaping FlutterResult) {
  99 + let status = AVCaptureDevice.authorizationStatus(for: .video)
  100 + switch status {
  101 + case .notDetermined:
  102 + result(0)
  103 + case .authorized:
  104 + result(1)
  105 + default:
  106 + result(2)
  107 + }
  108 + }
  109 +
  110 + func requestNative(_ call: FlutterMethodCall, _ result: @escaping FlutterResult) {
  111 + AVCaptureDevice.requestAccess(for: .video, completionHandler: { result($0) })
  112 + }
  113 +
  114 + func startNative(_ call: FlutterMethodCall, _ result: @escaping FlutterResult) {
  115 + textureId = registry.register(self)
  116 + captureSession = AVCaptureSession()
  117 + let position = call.arguments as! Int == 0 ? AVCaptureDevice.Position.front : .back
  118 + if #available(iOS 10.0, *) {
  119 + device = AVCaptureDevice.DiscoverySession(deviceTypes: [.builtInWideAngleCamera], mediaType: .video, position: position).devices.first
  120 + } else {
  121 + device = AVCaptureDevice.devices(for: .video).filter({$0.position == position}).first
  122 + }
  123 + device.addObserver(self, forKeyPath: #keyPath(AVCaptureDevice.torchMode), options: .new, context: nil)
  124 + captureSession.beginConfiguration()
  125 + // Add device input.
  126 + do {
  127 + let input = try AVCaptureDeviceInput(device: device)
  128 + captureSession.addInput(input)
  129 + } catch {
  130 + error.throwNative(result)
  131 + }
  132 + // Add video output.
  133 + let videoOutput = AVCaptureVideoDataOutput()
  134 + videoOutput.videoSettings = [kCVPixelBufferPixelFormatTypeKey as String: kCVPixelFormatType_32BGRA]
  135 + videoOutput.alwaysDiscardsLateVideoFrames = true
  136 + videoOutput.setSampleBufferDelegate(self, queue: DispatchQueue.main)
  137 + captureSession.addOutput(videoOutput)
  138 + for connection in videoOutput.connections {
  139 + connection.videoOrientation = .portrait
  140 + if position == .front && connection.isVideoMirroringSupported {
  141 + connection.isVideoMirrored = true
  142 + }
  143 + }
  144 + captureSession.commitConfiguration()
  145 + captureSession.startRunning()
  146 + let demensions = CMVideoFormatDescriptionGetDimensions(device.activeFormat.formatDescription)
  147 + let width = Double(demensions.height)
  148 + let height = Double(demensions.width)
  149 + let size = ["width": width, "height": height]
  150 + let answer: [String : Any?] = ["textureId": textureId, "size": size, "torchable": device.hasTorch]
  151 + result(answer)
  152 + }
  153 +
  154 + func torchNative(_ call: FlutterMethodCall, _ result: @escaping FlutterResult) {
  155 + do {
  156 + try device.lockForConfiguration()
  157 + device.torchMode = call.arguments as! Int == 1 ? .on : .off
  158 + device.unlockForConfiguration()
  159 + result(nil)
  160 + } catch {
  161 + error.throwNative(result)
  162 + }
  163 + }
  164 +
  165 + func analyzeNative(_ call: FlutterMethodCall, _ result: @escaping FlutterResult) {
  166 + analyzeMode = call.arguments as! Int
  167 + result(nil)
  168 + }
  169 +
  170 + func stopNative(_ result: FlutterResult) {
  171 + captureSession.stopRunning()
  172 + for input in captureSession.inputs {
  173 + captureSession.removeInput(input)
  174 + }
  175 + for output in captureSession.outputs {
  176 + captureSession.removeOutput(output)
  177 + }
  178 + device.removeObserver(self, forKeyPath: #keyPath(AVCaptureDevice.torchMode))
  179 + registry.unregisterTexture(textureId)
  180 +
  181 + analyzeMode = 0
  182 + latestBuffer = nil
  183 + captureSession = nil
  184 + device = nil
  185 + textureId = nil
  186 +
  187 + result(nil)
  188 + }
  189 +
  190 + public override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
  191 + switch keyPath {
  192 + case "torchMode":
  193 + // off = 0; on = 1; auto = 2;
  194 + let state = change?[.newKey] as? Int
  195 + let event: [String: Any?] = ["name": "torchState", "data": state]
  196 + sink?(event)
  197 + default:
  198 + break
  199 + }
  200 + }
14 } 201 }
  1 +//
  2 +// Util.swift
  3 +// camerax
  4 +//
  5 +// Created by 闫守旺 on 2021/2/6.
  6 +//
  7 +
  8 +import AVFoundation
  9 +import Flutter
  10 +import Foundation
  11 +import MLKitBarcodeScanning
  12 +
  13 +extension Error {
  14 + func throwNative(_ result: FlutterResult) {
  15 + let error = FlutterError(code: localizedDescription, message: nil, details: nil)
  16 + result(error)
  17 + }
  18 +}
  19 +
  20 +extension CVBuffer {
  21 + var image: UIImage {
  22 + let ciImage = CIImage(cvPixelBuffer: self)
  23 + let cgImage = CIContext().createCGImage(ciImage, from: ciImage.extent)
  24 + return UIImage(cgImage: cgImage!)
  25 + }
  26 +
  27 + var image1: UIImage {
  28 + // Lock the base address of the pixel buffer
  29 + CVPixelBufferLockBaseAddress(self, CVPixelBufferLockFlags.readOnly)
  30 + // Get the number of bytes per row for the pixel buffer
  31 + let baseAddress = CVPixelBufferGetBaseAddress(self)
  32 + // Get the number of bytes per row for the pixel buffer
  33 + let bytesPerRow = CVPixelBufferGetBytesPerRow(self)
  34 + // Get the pixel buffer width and height
  35 + let width = CVPixelBufferGetWidth(self)
  36 + let height = CVPixelBufferGetHeight(self)
  37 + // Create a device-dependent RGB color space
  38 + let colorSpace = CGColorSpaceCreateDeviceRGB()
  39 + // Create a bitmap graphics context with the sample buffer data
  40 + var bitmapInfo = CGBitmapInfo.byteOrder32Little.rawValue
  41 + bitmapInfo |= CGImageAlphaInfo.premultipliedFirst.rawValue & CGBitmapInfo.alphaInfoMask.rawValue
  42 + //let bitmapInfo: UInt32 = CGBitmapInfo.alphaInfoMask.rawValue
  43 + let context = CGContext(data: baseAddress, width: width, height: height, bitsPerComponent: 8, bytesPerRow: bytesPerRow, space: colorSpace, bitmapInfo: bitmapInfo)
  44 + // Create a Quartz image from the pixel data in the bitmap graphics context
  45 + let quartzImage = context?.makeImage()
  46 + // Unlock the pixel buffer
  47 + CVPixelBufferUnlockBaseAddress(self, CVPixelBufferLockFlags.readOnly)
  48 + // Create an image object from the Quartz image
  49 + return UIImage(cgImage: quartzImage!)
  50 + }
  51 +}
  52 +
  53 +extension UIDeviceOrientation {
  54 + func imageOrientation(position: AVCaptureDevice.Position) -> UIImage.Orientation {
  55 + switch self {
  56 + case .portrait:
  57 + return position == .front ? .leftMirrored : .right
  58 + case .landscapeLeft:
  59 + return position == .front ? .downMirrored : .up
  60 + case .portraitUpsideDown:
  61 + return position == .front ? .rightMirrored : .left
  62 + case .landscapeRight:
  63 + return position == .front ? .upMirrored : .down
  64 + default:
  65 + return .up
  66 + }
  67 + }
  68 +}
  69 +
  70 +extension Barcode {
  71 + var data: [String: Any?] {
  72 + let corners = cornerPoints?.map({$0.cgPointValue.data})
  73 + return ["corners": corners, "format": format.rawValue, "rawBytes": rawData, "rawValue": rawValue, "type": valueType.rawValue, "calendarEvent": calendarEvent?.data, "contactInfo": contactInfo?.data, "driverLicense": driverLicense?.data, "email": email?.data, "geoPoint": geoPoint?.data, "phone": phone?.data, "sms": sms?.data, "url": url?.data, "wifi": wifi?.data]
  74 + }
  75 +}
  76 +
  77 +extension CGPoint {
  78 + var data: [String: Any?] {
  79 + let x1 = NSNumber(value: x.native)
  80 + let y1 = NSNumber(value: y.native)
  81 + return ["x": x1, "y": y1]
  82 + }
  83 +}
  84 +
  85 +extension BarcodeCalendarEvent {
  86 + var data: [String: Any?] {
  87 + return ["description": eventDescription, "end": end?.rawValue, "location": location, "organizer": organizer, "start": start?.rawValue, "status": status, "summary": summary]
  88 + }
  89 +}
  90 +
  91 +extension Date {
  92 + var rawValue: String {
  93 + return ISO8601DateFormatter().string(from: self)
  94 + }
  95 +}
  96 +
  97 +extension BarcodeContactInfo {
  98 + var data: [String: Any?] {
  99 + return ["addresses": addresses?.map({$0.data}), "emails": emails?.map({$0.data}), "name": name?.data, "organization": organization, "phones": phones?.map({$0.data}), "title": jobTitle, "urls": urls]
  100 + }
  101 +}
  102 +
  103 +extension BarcodeAddress {
  104 + var data: [String: Any?] {
  105 + return ["addressLines": addressLines, "type": type.rawValue]
  106 + }
  107 +}
  108 +
  109 +extension BarcodePersonName {
  110 + var data: [String: Any?] {
  111 + return ["first": first, "formattedName": formattedName, "last": last, "middle": middle, "prefix": prefix, "pronunciation": pronunciation, "suffix": suffix]
  112 + }
  113 +}
  114 +
  115 +extension BarcodeDriverLicense {
  116 + var data: [String: Any?] {
  117 + return ["addressCity": addressCity, "addressState": addressState, "addressStreet": addressStreet, "addressZip": addressZip, "birthDate": birthDate, "documentType": documentType, "expiryDate": expiryDate, "firstName": firstName, "gender": gender, "issueDate": issuingDate, "issuingCountry": issuingCountry, "lastName": lastName, "licenseNumber": licenseNumber, "middleName": middleName]
  118 + }
  119 +}
  120 +
  121 +extension BarcodeEmail {
  122 + var data: [String: Any?] {
  123 + return ["address": address, "body": body, "subject": subject, "type": type.rawValue]
  124 + }
  125 +}
  126 +
  127 +extension BarcodeGeoPoint {
  128 + var data: [String: Any?] {
  129 + return ["latitude": latitude, "longitude": longitude]
  130 + }
  131 +}
  132 +
  133 +extension BarcodePhone {
  134 + var data: [String: Any?] {
  135 + return ["number": number, "type": type.rawValue]
  136 + }
  137 +}
  138 +
  139 +extension BarcodeSMS {
  140 + var data: [String: Any?] {
  141 + return ["message": message, "phoneNumber": phoneNumber]
  142 + }
  143 +}
  144 +
  145 +extension BarcodeURLBookmark {
  146 + var data: [String: Any?] {
  147 + return ["title": title, "url": url]
  148 + }
  149 +}
  150 +
  151 +extension BarcodeWifi {
  152 + var data: [String: Any?] {
  153 + return ["encryptionType": type.rawValue, "password": password, "ssid": ssid]
  154 + }
  155 +}
@@ -15,8 +15,9 @@ An universal scanner for Flutter based on MLKit. @@ -15,8 +15,9 @@ An universal scanner for Flutter based on MLKit.
15 s.source = { :path => '.' } 15 s.source = { :path => '.' }
16 s.source_files = 'Classes/**/*' 16 s.source_files = 'Classes/**/*'
17 s.dependency 'Flutter' 17 s.dependency 'Flutter'
18 - s.platform = :ios, '9.0'  
19 - 18 + s.dependency 'GoogleMLKit/BarcodeScanning'
  19 + s.platform = :ios, '10.0'
  20 + s.static_framework = true
20 # Flutter.framework does not contain a i386 slice. 21 # Flutter.framework does not contain a i386 slice.
21 s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES', 'EXCLUDED_ARCHS[sdk=iphonesimulator*]' => 'i386' } 22 s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES', 'EXCLUDED_ARCHS[sdk=iphonesimulator*]' => 'i386' }
22 s.swift_version = '5.0' 23 s.swift_version = '5.0'
1 -export 'src/mobile_scanner.dart';  
  1 +library mobile_scanner;
  2 +
  3 +export 'src/mobile_scanner.dart';
  4 +export 'src/camera_controller.dart';
  5 +export 'src/camera_view.dart';
  6 +export 'src/torch_state.dart';
  7 +export 'src/objects/barcode.dart';
  1 +import 'package:flutter/material.dart';
  2 +
  3 +/// Camera args for [CameraView].
  4 +class CameraArgs {
  5 + /// The texture id.
  6 + final int textureId;
  7 +
  8 + /// Size of the texture.
  9 + final Size size;
  10 +
  11 + /// Create a [CameraArgs].
  12 + CameraArgs(this.textureId, this.size);
  13 +}
  1 +import 'dart:async';
  2 +
  3 +import 'package:flutter/cupertino.dart';
  4 +import 'package:flutter/services.dart';
  5 +
  6 +import 'camera_args.dart';
  7 +import 'camera_facing.dart';
  8 +import 'objects/barcode.dart';
  9 +import 'torch_state.dart';
  10 +import 'util.dart';
  11 +
  12 +/// A camera controller.
  13 +abstract class CameraController {
  14 + /// Arguments for [CameraView].
  15 + ValueNotifier<CameraArgs?> get args;
  16 +
  17 + /// Torch state of the camera.
  18 + ValueNotifier<TorchState> get torchState;
  19 +
  20 + /// A stream of barcodes.
  21 + Stream<Barcode> get barcodes;
  22 +
  23 + /// Create a [CameraController].
  24 + ///
  25 + /// [facing] target facing used to select camera.
  26 + ///
  27 + /// [formats] the barcode formats for image analyzer.
  28 + factory CameraController([CameraFacing facing = CameraFacing.back]) =>
  29 + _CameraController(facing);
  30 +
  31 + /// Start the camera asynchronously.
  32 + Future<void> startAsync();
  33 +
  34 + /// Switch the torch's state.
  35 + void torch();
  36 +
  37 + /// Release the resources of the camera.
  38 + void dispose();
  39 +}
  40 +
  41 +class _CameraController implements CameraController {
  42 + static const MethodChannel method =
  43 + MethodChannel('dev.steenbakker.mobile_scanner/scanner/method');
  44 + static const EventChannel event =
  45 + EventChannel('dev.steenbakker.mobile_scanner/scanner/event');
  46 +
  47 + static const undetermined = 0;
  48 + static const authorized = 1;
  49 + static const denied = 2;
  50 +
  51 + static const analyze_none = 0;
  52 + static const analyze_barcode = 1;
  53 +
  54 + static int? id;
  55 + static StreamSubscription? subscription;
  56 +
  57 + final CameraFacing facing;
  58 + @override
  59 + final ValueNotifier<CameraArgs?> args;
  60 + @override
  61 + final ValueNotifier<TorchState> torchState;
  62 +
  63 + bool torchable;
  64 + late StreamController<Barcode> barcodesController;
  65 +
  66 + @override
  67 + Stream<Barcode> get barcodes => barcodesController.stream;
  68 +
  69 + _CameraController(this.facing)
  70 + : args = ValueNotifier(null),
  71 + torchState = ValueNotifier(TorchState.off),
  72 + torchable = false {
  73 + // In case new instance before dispose.
  74 + if (id != null) {
  75 + stop();
  76 + }
  77 + id = hashCode;
  78 + // Create barcode stream controller.
  79 + barcodesController = StreamController.broadcast(
  80 + onListen: () => tryAnalyze(analyze_barcode),
  81 + onCancel: () => tryAnalyze(analyze_none),
  82 + );
  83 + startAsync();
  84 + // Listen event handler.
  85 + subscription =
  86 + event.receiveBroadcastStream().listen((data) => handleEvent(data));
  87 + }
  88 +
  89 + void handleEvent(Map<dynamic, dynamic> event) {
  90 + final name = event['name'];
  91 + final data = event['data'];
  92 + switch (name) {
  93 + case 'torchState':
  94 + final state = TorchState.values[data];
  95 + torchState.value = state;
  96 + break;
  97 + case 'barcode':
  98 + final barcode = Barcode.fromNative(data);
  99 + barcodesController.add(barcode);
  100 + break;
  101 + default:
  102 + throw UnimplementedError();
  103 + }
  104 + }
  105 +
  106 + void tryAnalyze(int mode) {
  107 + if (hashCode != id) {
  108 + return;
  109 + }
  110 + method.invokeMethod('analyze', mode);
  111 + }
  112 +
  113 + @override
  114 + Future<void> startAsync() async {
  115 + ensure('startAsync');
  116 + // Check authorization state.
  117 + var state = await method.invokeMethod('state');
  118 + if (state == undetermined) {
  119 + final result = await method.invokeMethod('request');
  120 + state = result ? authorized : denied;
  121 + }
  122 + if (state != authorized) {
  123 + throw PlatformException(code: 'NO ACCESS');
  124 + }
  125 + // Start camera.
  126 + final answer =
  127 + await method.invokeMapMethod<String, dynamic>('start', facing.index);
  128 + final textureId = answer?['textureId'];
  129 + final size = toSize(answer?['size']);
  130 + args.value = CameraArgs(textureId, size);
  131 + torchable = answer?['torchable'];
  132 + }
  133 +
  134 + @override
  135 + void torch() {
  136 + ensure('torch');
  137 + if (!torchable) {
  138 + return;
  139 + }
  140 + var state =
  141 + torchState.value == TorchState.off ? TorchState.on : TorchState.off;
  142 + method.invokeMethod('torch', state.index);
  143 + }
  144 +
  145 + @override
  146 + void dispose() {
  147 + if (hashCode == id) {
  148 + stop();
  149 + subscription?.cancel();
  150 + subscription = null;
  151 + id = null;
  152 + }
  153 + barcodesController.close();
  154 + }
  155 +
  156 + void stop() => method.invokeMethod('stop');
  157 +
  158 + void ensure(String name) {
  159 + final message =
  160 + 'CameraController.$name called after CameraController.dispose\n'
  161 + 'CameraController methods should not be used after calling dispose.';
  162 + assert(hashCode == id, message);
  163 + }
  164 +}
  1 +/// The facing of a camera.
  2 +enum CameraFacing {
  3 + /// Front facing camera.
  4 + front,
  5 +
  6 + /// Back facing camera.
  7 + back,
  8 +}
  1 +import 'package:flutter/material.dart';
  2 +import 'package:mobile_scanner/mobile_scanner.dart';
  3 +
  4 +import 'camera_args.dart';
  5 +import 'camera_controller.dart';
  6 +
  7 +/// A widget showing a live camera preview.
  8 +class CameraView extends StatelessWidget {
  9 + /// The controller of the camera.
  10 + final CameraController controller;
  11 + final Function(Barcode barcode, CameraArgs args)? onDetect;
  12 +
  13 + /// Create a [CameraView] with a [controller], the [controller] must has been initialized.
  14 + CameraView(this.controller, {this.onDetect});
  15 +
  16 + @override
  17 + Widget build(BuildContext context) {
  18 + return ValueListenableBuilder(
  19 + valueListenable: controller.args,
  20 + builder: (context, value, child) => _build(context, value as CameraArgs?),
  21 + );
  22 + }
  23 +
  24 + Widget _build(BuildContext context, CameraArgs? value) {
  25 + if (value == null) {
  26 + return Container(color: Colors.black);
  27 + } else {
  28 +
  29 + controller.barcodes.listen((a) => onDetect!(a, value));
  30 +
  31 + return ClipRect(
  32 + child: Transform.scale(
  33 + scale: value.size.fill(MediaQuery.of(context) .size),
  34 + child: Center(
  35 + child: AspectRatio(
  36 + aspectRatio: value.size.aspectRatio,
  37 + child: Texture(textureId: value.textureId),
  38 + ),
  39 + ),
  40 + ),
  41 + );
  42 + }
  43 + }
  44 +}
  45 +
  46 +extension on Size {
  47 + double fill(Size targetSize) {
  48 + if (targetSize.aspectRatio < aspectRatio) {
  49 + return targetSize.height * aspectRatio / targetSize.width;
  50 + } else {
  51 + return targetSize.width / aspectRatio / targetSize.height;
  52 + }
  53 + }
  54 +}
  1 +import 'dart:async';
  2 +
1 import 'package:flutter/material.dart'; 3 import 'package:flutter/material.dart';
2 import 'package:mobile_scanner/src/objects/preview_details.dart'; 4 import 'package:mobile_scanner/src/objects/preview_details.dart';
3 import 'package:native_device_orientation/native_device_orientation.dart'; 5 import 'package:native_device_orientation/native_device_orientation.dart';
  6 +import 'package:sensors_plus/sensors_plus.dart';
4 7
5 -class Preview extends StatelessWidget { 8 +class Preview extends StatefulWidget {
6 final double width, height; 9 final double width, height;
7 final double targetWidth, targetHeight; 10 final double targetWidth, targetHeight;
8 final int? textureId; 11 final int? textureId;
@@ -18,47 +21,103 @@ class Preview extends StatelessWidget { @@ -18,47 +21,103 @@ class Preview extends StatelessWidget {
18 }) : textureId = previewDetails.textureId, 21 }) : textureId = previewDetails.textureId,
19 width = previewDetails.width!.toDouble(), 22 width = previewDetails.width!.toDouble(),
20 height = previewDetails.height!.toDouble(), 23 height = previewDetails.height!.toDouble(),
21 - sensorOrientation = previewDetails.sensorOrientation as int?, super(key: key); 24 + sensorOrientation = previewDetails.sensorOrientation as int?,
  25 + super(key: key);
  26 +
  27 + @override
  28 + State<Preview> createState() => _PreviewState();
  29 +}
  30 +
  31 +class _PreviewState extends State<Preview> {
  32 +
  33 + final _streamSubscriptions = <StreamSubscription<dynamic>>[];
  34 + bool landscapeLeft = false;
  35 +
  36 + @override
  37 + void initState() {
  38 + super.initState();
  39 + _streamSubscriptions.add(
  40 + magnetometerEvents.listen(
  41 + (MagnetometerEvent event) {
  42 + if (event.x <= 0) {
  43 + landscapeLeft = true;
  44 + } else {
  45 + landscapeLeft = false;
  46 + }
  47 + },
  48 + ),
  49 + );
  50 + }
  51 +
  52 + @override
  53 + void dispose() {
  54 + super.dispose();
  55 + for (final subscription in _streamSubscriptions) {
  56 + subscription.cancel();
  57 + }
  58 + }
  59 +
  60 +
  61 + int _getRotationCompensation(NativeDeviceOrientation nativeOrientation) {
  62 + int nativeRotation = 0;
  63 + switch (nativeOrientation) {
  64 + case NativeDeviceOrientation.portraitUp:
  65 + nativeRotation = 0;
  66 + break;
  67 + case NativeDeviceOrientation.landscapeRight:
  68 + nativeRotation = 90;
  69 + break;
  70 + case NativeDeviceOrientation.portraitDown:
  71 + nativeRotation = 180;
  72 + break;
  73 + case NativeDeviceOrientation.landscapeLeft:
  74 + nativeRotation = 270;
  75 + break;
  76 + case NativeDeviceOrientation.unknown:
  77 + default:
  78 + break;
  79 + }
  80 +
  81 + return ((nativeRotation - widget.sensorOrientation! + 450) % 360) ~/ 90;
  82 + }
22 83
23 @override 84 @override
24 Widget build(BuildContext context) { 85 Widget build(BuildContext context) {
  86 + final orientation = MediaQuery.of(context).orientation;
  87 + double frameHeight = widget.width;
  88 + double frameWidth = widget.height;
  89 +
  90 + return ClipRect(
  91 + child: FittedBox(
  92 + fit: widget.fit,
  93 + child: RotatedBox(
  94 + quarterTurns: orientation == Orientation.landscape ? landscapeLeft ? 1 : 3 : 0,
  95 + child: SizedBox(
  96 + width: frameWidth,
  97 + height: frameHeight,
  98 + child: Texture(textureId: widget.textureId!),
  99 + ),
  100 + ),
  101 + ),
  102 + );
  103 +
25 return NativeDeviceOrientationReader( 104 return NativeDeviceOrientationReader(
26 builder: (context) { 105 builder: (context) {
27 - var nativeOrientation = NativeDeviceOrientationReader.orientation(context);  
28 -  
29 - int nativeRotation = 0;  
30 - switch (nativeOrientation) {  
31 - case NativeDeviceOrientation.portraitUp:  
32 - nativeRotation = 0;  
33 - break;  
34 - case NativeDeviceOrientation.landscapeRight:  
35 - nativeRotation = 90;  
36 - break;  
37 - case NativeDeviceOrientation.portraitDown:  
38 - nativeRotation = 180;  
39 - break;  
40 - case NativeDeviceOrientation.landscapeLeft:  
41 - nativeRotation = 270;  
42 - break;  
43 - case NativeDeviceOrientation.unknown:  
44 - default:  
45 - break;  
46 - }  
47 -  
48 - int rotationCompensation = ((nativeRotation - sensorOrientation! + 450) % 360) ~/ 90;  
49 -  
50 - double frameHeight = width;  
51 - double frameWidth = height; 106 + var nativeOrientation =
  107 + NativeDeviceOrientationReader.orientation(context);
  108 +
  109 + double frameHeight = widget.width;
  110 + double frameWidth = widget.height;
52 111
53 return ClipRect( 112 return ClipRect(
54 child: FittedBox( 113 child: FittedBox(
55 - fit: fit, 114 + fit: widget.fit,
56 child: RotatedBox( 115 child: RotatedBox(
57 - quarterTurns: rotationCompensation, 116 + quarterTurns: _getRotationCompensation(nativeOrientation),
58 child: SizedBox( 117 child: SizedBox(
59 width: frameWidth, 118 width: frameWidth,
60 height: frameHeight, 119 height: frameHeight,
61 - child: Texture(textureId: textureId!), 120 + child: Texture(textureId: widget.textureId!),
62 ), 121 ),
63 ), 122 ),
64 ), 123 ),
  1 +import 'dart:typed_data';
  2 +import 'dart:ui';
  3 +
  4 +import '../util.dart';
  5 +
  6 +/// Represents a single recognized barcode and its value.
  7 +class Barcode {
  8 + /// Returns four corner points in clockwise direction starting with top-left.
  9 + ///
  10 + /// Due to the possible perspective distortions, this is not necessarily a rectangle.
  11 + ///
  12 + /// Returns null if the corner points can not be determined.
  13 + final List<Offset>? corners;
  14 +
  15 + /// Returns barcode format
  16 + final BarcodeFormat format;
  17 +
  18 + /// Returns raw bytes as it was encoded in the barcode.
  19 + ///
  20 + /// Returns null if the raw bytes can not be determined.
  21 + final Uint8List rawBytes;
  22 +
  23 + /// Returns barcode value as it was encoded in the barcode. Structured values are not parsed, for example: 'MEBKM:TITLE:Google;URL://www.google.com;;'.
  24 + ///
  25 + /// It's only available when the barcode is encoded in the UTF-8 format, and for non-UTF8 ones use [rawBytes] instead.
  26 + ///
  27 + /// Returns null if the raw value can not be determined.
  28 + final String rawValue;
  29 +
  30 + /// Returns format type of the barcode value.
  31 + ///
  32 + /// For example, TYPE_TEXT, TYPE_PRODUCT, TYPE_URL, etc.
  33 + ///
  34 + /// If the value structure cannot be parsed, TYPE_TEXT will be returned. If the recognized structure type is not defined in your current version of SDK, TYPE_UNKNOWN will be returned.
  35 + ///
  36 + /// Note that the built-in parsers only recognize a few popular value structures. For your specific use case, you might want to directly consume rawValue and implement your own parsing logic.
  37 + final BarcodeType type;
  38 +
  39 + /// Gets parsed calendar event details.
  40 + final CalendarEvent? calendarEvent;
  41 +
  42 + /// Gets parsed contact details.
  43 + final ContactInfo? contactInfo;
  44 +
  45 + /// Gets parsed driver license details.
  46 + final DriverLicense? driverLicense;
  47 +
  48 + /// Gets parsed email details.
  49 + final Email? email;
  50 +
  51 + /// Gets parsed geo coordinates.
  52 + final GeoPoint? geoPoint;
  53 +
  54 + /// Gets parsed phone number details.
  55 + final Phone? phone;
  56 +
  57 + /// Gets parsed SMS details.
  58 + final SMS? sms;
  59 +
  60 + /// Gets parsed URL bookmark details.
  61 + final UrlBookmark? url;
  62 +
  63 + /// Gets parsed WiFi AP details.
  64 + final WiFi? wifi;
  65 +
  66 + /// Create a [Barcode] from native data.
  67 + Barcode.fromNative(Map<dynamic, dynamic> data)
  68 + : corners = toCorners(data['corners']),
  69 + format = toFormat(data['format']),
  70 + rawBytes = data['rawBytes'],
  71 + rawValue = data['rawValue'],
  72 + type = BarcodeType.values[data['type']],
  73 + calendarEvent = toCalendarEvent(data['calendarEvent']),
  74 + contactInfo = toContactInfo(data['contactInfo']),
  75 + driverLicense = toDriverLicense(data['driverLicense']),
  76 + email = toEmail(data['email']),
  77 + geoPoint = toGeoPoint(data['geoPoint']),
  78 + phone = toPhone(data['phone']),
  79 + sms = toSMS(data['sms']),
  80 + url = toUrl(data['url']),
  81 + wifi = toWiFi(data['wifi']);
  82 +}
  83 +
  84 +/// A calendar event extracted from QRCode.
  85 +class CalendarEvent {
  86 + /// Gets the description of the calendar event.
  87 + ///
  88 + /// Returns null if not available.
  89 + final String? description;
  90 +
  91 + /// Gets the start date time of the calendar event.
  92 + ///
  93 + /// Returns null if not available.
  94 + final DateTime? start;
  95 +
  96 + /// Gets the end date time of the calendar event.
  97 + ///
  98 + /// Returns null if not available.
  99 + final DateTime? end;
  100 +
  101 + /// Gets the location of the calendar event.
  102 + ///
  103 + /// Returns null if not available.
  104 + final String? location;
  105 +
  106 + /// Gets the organizer of the calendar event.
  107 + ///
  108 + /// Returns null if not available.
  109 + final String? organizer;
  110 +
  111 + /// Gets the status of the calendar event.
  112 + ///
  113 + /// Returns null if not available.
  114 + final String? status;
  115 +
  116 + /// Gets the summary of the calendar event.
  117 + ///
  118 + /// Returns null if not available.
  119 + final String? summary;
  120 +
  121 + /// Create a [CalendarEvent] from native data.
  122 + CalendarEvent.fromNative(Map<dynamic, dynamic> data)
  123 + : description = data['description'],
  124 + start = DateTime.tryParse(data['start']),
  125 + end = DateTime.tryParse(data['end']),
  126 + location = data['location'],
  127 + organizer = data['organizer'],
  128 + status = data['status'],
  129 + summary = data['summary'];
  130 +}
  131 +
  132 +/// A person's or organization's business card. For example a VCARD.
  133 +class ContactInfo {
  134 + /// Gets contact person's addresses.
  135 + ///
  136 + /// Returns an empty list if nothing found.
  137 + final List<Address> addresses;
  138 +
  139 + /// Gets contact person's emails.
  140 + ///
  141 + /// Returns an empty list if nothing found.
  142 + final List<Email> emails;
  143 +
  144 + /// Gets contact person's name.
  145 + ///
  146 + /// Returns null if not available.
  147 + final PersonName? name;
  148 +
  149 + /// Gets contact person's organization.
  150 + ///
  151 + /// Returns null if not available.
  152 + final String organization;
  153 +
  154 + /// Gets contact person's phones.
  155 + ///
  156 + /// Returns an empty list if nothing found.
  157 + final List<Phone> phones;
  158 +
  159 + /// Gets contact person's title.
  160 + ///
  161 + /// Returns null if not available.
  162 + final String title;
  163 +
  164 + /// Gets contact person's urls.
  165 + ///
  166 + /// Returns an empty list if nothing found.
  167 + final List<String> urls;
  168 +
  169 + /// Create a [ContactInfo] from native data.
  170 + ContactInfo.fromNative(Map<dynamic, dynamic> data)
  171 + : addresses = List.unmodifiable(
  172 + data['addresses'].map((e) => Address.fromNative(e))),
  173 + emails =
  174 + List.unmodifiable(data['emails'].map((e) => Email.fromNative(e))),
  175 + name = toName(data['name']),
  176 + organization = data['organization'],
  177 + phones =
  178 + List.unmodifiable(data['phones'].map((e) => Phone.fromNative(e))),
  179 + title = data['title'],
  180 + urls = List.unmodifiable(data['urls']);
  181 +}
  182 +
  183 +/// An address.
  184 +class Address {
  185 + /// Gets formatted address, multiple lines when appropriate. This field always contains at least one line.
  186 + final List<String> addressLines;
  187 +
  188 + /// Gets type of the address.
  189 + final AddressType type;
  190 +
  191 + /// Create a [Address] from native data.
  192 + Address.fromNative(Map<dynamic, dynamic> data)
  193 + : addressLines = List.unmodifiable(data['addressLines']),
  194 + type = AddressType.values[data['type']];
  195 +}
  196 +
  197 +/// A person's name, both formatted version and individual name components.
  198 +class PersonName {
  199 + /// Gets first name.
  200 + ///
  201 + /// Returns null if not available.
  202 + final String first;
  203 +
  204 + /// Gets middle name.
  205 + ///
  206 + /// Returns null if not available.
  207 + final String middle;
  208 +
  209 + /// Gets last name.
  210 + ///
  211 + /// Returns null if not available.
  212 + final String last;
  213 +
  214 + /// Gets prefix of the name.
  215 + ///
  216 + /// Returns null if not available.
  217 + final String prefix;
  218 +
  219 + /// Gets suffix of the person's name.
  220 + ///
  221 + /// Returns null if not available.
  222 + final String suffix;
  223 +
  224 + /// Gets the properly formatted name.
  225 + ///
  226 + /// Returns null if not available.
  227 + final String formattedName;
  228 +
  229 + /// Designates a text string to be set as the kana name in the phonebook. Used for Japanese contacts.
  230 + ///
  231 + /// Returns null if not available.
  232 + final String pronunciation;
  233 +
  234 + /// Create a [PersonName] from native data.
  235 + PersonName.fromNative(Map<dynamic, dynamic> data)
  236 + : first = data['first'],
  237 + middle = data['middle'],
  238 + last = data['last'],
  239 + prefix = data['prefix'],
  240 + suffix = data['suffix'],
  241 + formattedName = data['formattedName'],
  242 + pronunciation = data['pronunciation'];
  243 +}
  244 +
  245 +/// A driver license or ID card.
  246 +class DriverLicense {
  247 + /// Gets city of holder's address.
  248 + ///
  249 + /// Returns null if not available.
  250 + final String addressCity;
  251 +
  252 + /// Gets state of holder's address.
  253 + ///
  254 + /// Returns null if not available.
  255 + final String addressState;
  256 +
  257 + /// Gets holder's street address.
  258 + ///
  259 + /// Returns null if not available.
  260 + final String addressStreet;
  261 +
  262 + /// Gets postal code of holder's address.
  263 + ///
  264 + /// Returns null if not available.
  265 + final String addressZip;
  266 +
  267 + /// Gets birth date of the holder.
  268 + ///
  269 + /// Returns null if not available.
  270 + final String birthDate;
  271 +
  272 + /// Gets "DL" for driver licenses, "ID" for ID cards.
  273 + ///
  274 + /// Returns null if not available.
  275 + final String documentType;
  276 +
  277 + /// Gets expiry date of the license.
  278 + ///
  279 + /// Returns null if not available.
  280 + final String expiryDate;
  281 +
  282 + /// Gets holder's first name.
  283 + ///
  284 + /// Returns null if not available.
  285 + final String firstName;
  286 +
  287 + /// Gets holder's gender. 1 - male, 2 - female.
  288 + ///
  289 + /// Returns null if not available.
  290 + final String gender;
  291 +
  292 + /// Gets issue date of the license.
  293 + ///
  294 + /// The date format depends on the issuing country. MMDDYYYY for the US, YYYYMMDD for Canada.
  295 + ///
  296 + /// Returns null if not available.
  297 + final String issueDate;
  298 +
  299 + /// Gets the three-letter country code in which DL/ID was issued.
  300 + ///
  301 + /// Returns null if not available.
  302 + final String issuingCountry;
  303 +
  304 + /// Gets holder's last name.
  305 + ///
  306 + /// Returns null if not available.
  307 + final String lastName;
  308 +
  309 + /// Gets driver license ID number.
  310 + ///
  311 + /// Returns null if not available.
  312 + final String licenseNumber;
  313 +
  314 + /// Gets holder's middle name.
  315 + ///
  316 + /// Returns null if not available.
  317 + final String middleName;
  318 +
  319 + /// Create a [DriverLicense] from native data.
  320 + DriverLicense.fromNative(Map<dynamic, dynamic> data)
  321 + : addressCity = data['addressCity'],
  322 + addressState = data['addressState'],
  323 + addressStreet = data['addressStreet'],
  324 + addressZip = data['addressZip'],
  325 + birthDate = data['birthDate'],
  326 + documentType = data['documentType'],
  327 + expiryDate = data['expiryDate'],
  328 + firstName = data['firstName'],
  329 + gender = data['gender'],
  330 + issueDate = data['issueDate'],
  331 + issuingCountry = data['issuingCountry'],
  332 + lastName = data['lastName'],
  333 + licenseNumber = data['licenseNumber'],
  334 + middleName = data['middleName'];
  335 +}
  336 +
  337 +/// An email message from a 'MAILTO:' or similar QRCode type.
  338 +class Email {
  339 + /// Gets email's address.
  340 + ///
  341 + /// Returns null if not available.
  342 + final String address;
  343 +
  344 + /// Gets email's body.
  345 + ///
  346 + /// Returns null if not available.
  347 + final String body;
  348 +
  349 + /// Gets email's subject.
  350 + ///
  351 + /// Returns null if not available.
  352 + final String subject;
  353 +
  354 + /// Gets type of the email.
  355 + ///
  356 + /// See also [EmailType].
  357 + final EmailType type;
  358 +
  359 + /// Create a [Email] from native data.
  360 + Email.fromNative(Map<dynamic, dynamic> data)
  361 + : address = data['address'],
  362 + body = data['body'],
  363 + subject = data['subject'],
  364 + type = EmailType.values[data['type']];
  365 +}
  366 +
  367 +/// GPS coordinates from a 'GEO:' or similar QRCode type.
  368 +class GeoPoint {
  369 + /// Gets the latitude.
  370 + final double latitude;
  371 +
  372 + /// Gets the longitude.
  373 + final double longitude;
  374 +
  375 + /// Create a [GeoPoint] from native data.
  376 + GeoPoint.fromNative(Map<dynamic, dynamic> data)
  377 + : latitude = data['latitude'],
  378 + longitude = data['longitude'];
  379 +}
  380 +
  381 +/// Phone number info.
  382 +class Phone {
  383 + /// Gets phone number.
  384 + ///
  385 + /// Returns null if not available.
  386 + final String number;
  387 +
  388 + /// Gets type of the phone number.
  389 + ///
  390 + /// See also [PhoneType].
  391 + final PhoneType type;
  392 +
  393 + /// Create a [Phone] from native data.
  394 + Phone.fromNative(Map<dynamic, dynamic> data)
  395 + : number = data['number'],
  396 + type = PhoneType.values[data['type']];
  397 +}
  398 +
  399 +/// A sms message from a 'SMS:' or similar QRCode type.
  400 +class SMS {
  401 + /// Gets the message content of the sms.
  402 + ///
  403 + /// Returns null if not available.
  404 + final String message;
  405 +
  406 + /// Gets the phone number of the sms.
  407 + ///
  408 + /// Returns null if not available.
  409 + final String phoneNumber;
  410 +
  411 + /// Create a [SMS] from native data.
  412 + SMS.fromNative(Map<dynamic, dynamic> data)
  413 + : message = data['message'],
  414 + phoneNumber = data['phoneNumber'];
  415 +}
  416 +
  417 +/// A URL and title from a 'MEBKM:' or similar QRCode type.
  418 +class UrlBookmark {
  419 + /// Gets the title of the bookmark.
  420 + ///
  421 + /// Returns null if not available.
  422 + final String title;
  423 +
  424 + /// Gets the url of the bookmark.
  425 + ///
  426 + /// Returns null if not available.
  427 + final String url;
  428 +
  429 + /// Create a [UrlBookmark] from native data.
  430 + UrlBookmark.fromNative(Map<dynamic, dynamic> data)
  431 + : title = data['title'],
  432 + url = data['url'];
  433 +}
  434 +
  435 +/// A wifi network parameters from a 'WIFI:' or similar QRCode type.
  436 +class WiFi {
  437 + /// Gets the encryption type of the WIFI.
  438 + ///
  439 + /// See all [EncryptionType].
  440 + final EncryptionType encryptionType;
  441 +
  442 + /// Gets the ssid of the WIFI.
  443 + ///
  444 + /// Returns null if not available.
  445 + final String ssid;
  446 +
  447 + /// Gets the password of the WIFI.
  448 + ///
  449 + /// Returns null if not available.
  450 + final String password;
  451 +
  452 + /// Create a [WiFi] from native data.
  453 + WiFi.fromNative(Map<dynamic, dynamic> data)
  454 + : encryptionType = EncryptionType.values[data['encryptionType']],
  455 + ssid = data['ssid'],
  456 + password = data['password'];
  457 +}
  458 +
  459 +enum BarcodeFormat {
  460 + /// Barcode format unknown to the current SDK.
  461 + ///
  462 + /// Constant Value: -1
  463 + unknown,
  464 +
  465 + /// Barcode format constant representing the union of all supported formats.
  466 + ///
  467 + /// Constant Value: 0
  468 + all,
  469 +
  470 + /// Barcode format constant for Code 128.
  471 + ///
  472 + /// Constant Value: 1
  473 + code128,
  474 +
  475 + /// Barcode format constant for Code 39.
  476 + ///
  477 + /// Constant Value: 2
  478 + code39,
  479 +
  480 + /// Barcode format constant for Code 93.
  481 + ///
  482 + /// Constant Value: 4
  483 + code93,
  484 +
  485 + /// Barcode format constant for Codabar.
  486 + ///
  487 + /// Constant Value: 8
  488 + codebar,
  489 +
  490 + /// Barcode format constant for Data Matrix.
  491 + ///
  492 + /// Constant Value: 16
  493 + data_matrix,
  494 +
  495 + /// Barcode format constant for EAN-13.
  496 + ///
  497 + /// Constant Value: 32
  498 + ean13,
  499 +
  500 + /// Barcode format constant for EAN-8.
  501 + ///
  502 + /// Constant Value: 64
  503 + ean8,
  504 +
  505 + /// Barcode format constant for ITF (Interleaved Two-of-Five).
  506 + ///
  507 + /// Constant Value: 128
  508 + itf,
  509 +
  510 + /// Barcode format constant for QR Code.
  511 + ///
  512 + /// Constant Value: 256
  513 + qr_code,
  514 +
  515 + /// Barcode format constant for UPC-A.
  516 + ///
  517 + /// Constant Value: 512
  518 + upc_a,
  519 +
  520 + /// Barcode format constant for UPC-E.
  521 + ///
  522 + /// Constant Value: 1024
  523 + upc_e,
  524 +
  525 + /// Barcode format constant for PDF-417.
  526 + ///
  527 + /// Constant Value: 2048
  528 + pdf417,
  529 +
  530 + /// Barcode format constant for AZTEC.
  531 + ///
  532 + /// Constant Value: 4096
  533 + aztec,
  534 +}
  535 +
  536 +/// Address type constants.
  537 +enum AddressType {
  538 + /// Unknown address type.
  539 + ///
  540 + /// Constant Value: 0
  541 + unknown,
  542 +
  543 + /// Work address.
  544 + ///
  545 + /// Constant Value: 1
  546 + work,
  547 +
  548 + /// Home address.
  549 + ///
  550 + /// Constant Value: 2
  551 + home,
  552 +}
  553 +
  554 +/// Barcode value type constants
  555 +enum BarcodeType {
  556 + /// Barcode value type unknown, which indicates the current version of SDK cannot recognize the structure of the barcode. Developers can inspect the raw value instead.
  557 + ///
  558 + /// Constant Value: 0
  559 + unknown,
  560 +
  561 + /// Barcode value type constant for contact information.
  562 + ///
  563 + /// Constant Value: 1
  564 + contactInfo,
  565 +
  566 + /// Barcode value type constant for email message details.
  567 + ///
  568 + /// Constant Value: 2
  569 + email,
  570 +
  571 + /// Barcode value type constant for ISBNs.
  572 + ///
  573 + /// Constant Value: 3
  574 + isbn,
  575 +
  576 + /// Barcode value type constant for phone numbers.
  577 + ///
  578 + /// Constant Value: 4
  579 + phone,
  580 +
  581 + /// Barcode value type constant for product codes.
  582 + ///
  583 + /// Constant Value: 5
  584 + product,
  585 +
  586 + /// Barcode value type constant for SMS details.
  587 + ///
  588 + /// Constant Value: 6
  589 + sms,
  590 +
  591 + /// Barcode value type constant for plain text.
  592 + ///
  593 + ///Constant Value: 7
  594 + text,
  595 +
  596 + /// Barcode value type constant for URLs/bookmarks.
  597 + ///
  598 + /// Constant Value: 8
  599 + url,
  600 +
  601 + /// Barcode value type constant for WiFi access point details.
  602 + ///
  603 + /// Constant Value: 9
  604 + wifi,
  605 +
  606 + /// Barcode value type constant for geographic coordinates.
  607 + ///
  608 + /// Constant Value: 10
  609 + geo,
  610 +
  611 + /// Barcode value type constant for calendar events.
  612 + ///
  613 + /// Constant Value: 11
  614 + calendarEvent,
  615 +
  616 + /// Barcode value type constant for driver's license data.
  617 + ///
  618 + /// Constant Value: 12
  619 + driverLicense,
  620 +}
  621 +
  622 +/// Email format type constants.
  623 +enum EmailType {
  624 + /// Unknown email type.
  625 + ///
  626 + /// Constant Value: 0
  627 + unknown,
  628 +
  629 + /// Work email.
  630 + ///
  631 + /// Constant Value: 1
  632 + work,
  633 +
  634 + /// Home email.
  635 + ///
  636 + /// Constant Value: 2
  637 + home,
  638 +}
  639 +
  640 +/// Phone number format type constants.
  641 +enum PhoneType {
  642 + /// Unknown phone type.
  643 + ///
  644 + /// Constant Value: 0
  645 + unknown,
  646 +
  647 + /// Work phone.
  648 + ///
  649 + /// Constant Value: 1
  650 + work,
  651 +
  652 + /// Home phone.
  653 + ///
  654 + /// Constant Value: 2
  655 + home,
  656 +
  657 + /// Fax machine.
  658 + ///
  659 + /// Constant Value: 3
  660 + fax,
  661 +
  662 + /// Mobile phone.
  663 + ///
  664 + /// Constant Value: 4
  665 + mobile,
  666 +}
  667 +
  668 +/// Wifi encryption type constants.
  669 +enum EncryptionType {
  670 + /// Unknown encryption type.
  671 + ///
  672 + /// Constant Value: 0
  673 + none,
  674 +
  675 + /// Not encrypted.
  676 + ///
  677 + /// Constant Value: 1
  678 + open,
  679 +
  680 + /// WPA level encryption.
  681 + ///
  682 + /// Constant Value: 2
  683 + wpa,
  684 +
  685 + /// WEP level encryption.
  686 + ///
  687 + /// Constant Value: 3
  688 + wep,
  689 +}
  1 +/// The state of torch.
  2 +enum TorchState {
  3 + /// Torch is off.
  4 + off,
  5 +
  6 + /// Torch is on.
  7 + on,
  8 +}
  1 +import 'package:flutter/material.dart';
  2 +
  3 +import 'objects/barcode.dart';
  4 +
  5 +Size toSize(Map<dynamic, dynamic> data) {
  6 + final width = data['width'];
  7 + final height = data['height'];
  8 + return Size(width, height);
  9 +}
  10 +
  11 +List<Offset>? toCorners(List<dynamic>? data) {
  12 + if (data != null) {
  13 + return List.unmodifiable(data.map((e) => Offset(e['x'], e['y'])));
  14 + } else {
  15 + return null;
  16 + }
  17 +}
  18 +
  19 +BarcodeFormat toFormat(int value) {
  20 + switch (value) {
  21 + case 0:
  22 + return BarcodeFormat.all;
  23 + case 1:
  24 + return BarcodeFormat.code128;
  25 + case 2:
  26 + return BarcodeFormat.code39;
  27 + case 4:
  28 + return BarcodeFormat.code93;
  29 + case 8:
  30 + return BarcodeFormat.codebar;
  31 + case 16:
  32 + return BarcodeFormat.data_matrix;
  33 + case 32:
  34 + return BarcodeFormat.ean13;
  35 + case 64:
  36 + return BarcodeFormat.ean8;
  37 + case 128:
  38 + return BarcodeFormat.itf;
  39 + case 256:
  40 + return BarcodeFormat.qr_code;
  41 + case 512:
  42 + return BarcodeFormat.upc_a;
  43 + case 1024:
  44 + return BarcodeFormat.upc_e;
  45 + case 2048:
  46 + return BarcodeFormat.pdf417;
  47 + case 4096:
  48 + return BarcodeFormat.aztec;
  49 + default:
  50 + return BarcodeFormat.unknown;
  51 + }
  52 +}
  53 +
  54 +CalendarEvent? toCalendarEvent(Map<dynamic, dynamic>? data) {
  55 + if (data != null) {
  56 + return CalendarEvent.fromNative(data);
  57 + } else {
  58 + return null;
  59 + }
  60 +}
  61 +
  62 +DateTime? toDateTime(Map<dynamic, dynamic>? data) {
  63 + if (data != null) {
  64 + final year = data['year'];
  65 + final month = data['month'];
  66 + final day = data['day'];
  67 + final hour = data['hours'];
  68 + final minute = data['minutes'];
  69 + final second = data['seconds'];
  70 + return data['isUtc']
  71 + ? DateTime.utc(year, month, day, hour, minute, second)
  72 + : DateTime(year, month, day, hour, minute, second);
  73 + } else {
  74 + return null;
  75 + }
  76 +}
  77 +
  78 +ContactInfo? toContactInfo(Map<dynamic, dynamic>? data) {
  79 + if (data != null) {
  80 + return ContactInfo.fromNative(data);
  81 + } else {
  82 + return null;
  83 + }
  84 +}
  85 +
  86 +PersonName? toName(Map<dynamic, dynamic>? data) {
  87 + if (data != null) {
  88 + return PersonName.fromNative(data);
  89 + } else {
  90 + return null;
  91 + }
  92 +}
  93 +
  94 +DriverLicense? toDriverLicense(Map<dynamic, dynamic>? data) {
  95 + if (data != null) {
  96 + return DriverLicense.fromNative(data);
  97 + } else {
  98 + return null;
  99 + }
  100 +}
  101 +
  102 +Email? toEmail(Map<dynamic, dynamic>? data) {
  103 + if (data != null) {
  104 + return Email.fromNative(data);
  105 + } else {
  106 + return null;
  107 + }
  108 +}
  109 +
  110 +GeoPoint? toGeoPoint(Map<dynamic, dynamic>? data) {
  111 + if (data != null) {
  112 + return GeoPoint.fromNative(data);
  113 + } else {
  114 + return null;
  115 + }
  116 +}
  117 +
  118 +Phone? toPhone(Map<dynamic, dynamic>? data) {
  119 + if (data != null) {
  120 + return Phone.fromNative(data);
  121 + } else {
  122 + return null;
  123 + }
  124 +}
  125 +
  126 +SMS? toSMS(Map<dynamic, dynamic>? data) {
  127 + if (data != null) {
  128 + return SMS.fromNative(data);
  129 + } else {
  130 + return null;
  131 + }
  132 +}
  133 +
  134 +UrlBookmark? toUrl(Map<dynamic, dynamic>? data) {
  135 + if (data != null) {
  136 + return UrlBookmark.fromNative(data);
  137 + } else {
  138 + return null;
  139 + }
  140 +}
  141 +
  142 +WiFi? toWiFi(Map<dynamic, dynamic>? data) {
  143 + if (data != null) {
  144 + return WiFi.fromNative(data);
  145 + } else {
  146 + return null;
  147 + }
  148 +}
@@ -9,6 +9,7 @@ environment: @@ -9,6 +9,7 @@ environment:
9 9
10 dependencies: 10 dependencies:
11 native_device_orientation: ^1.0.0 11 native_device_orientation: ^1.0.0
  12 + sensors_plus: ^1.2.1
12 flutter: 13 flutter:
13 sdk: flutter 14 sdk: flutter
14 15
@@ -17,7 +17,4 @@ void main() { @@ -17,7 +17,4 @@ void main() {
17 channel.setMockMethodCallHandler(null); 17 channel.setMockMethodCallHandler(null);
18 }); 18 });
19 19
20 - test('getPlatformVersion', () async {  
21 - expect(await MobileScannerHandler.platformVersion, '42');  
22 - });  
23 } 20 }