Showing
32 changed files
with
2582 additions
and
105 deletions
| @@ -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) | 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 | ||
| 22 | } | 24 | } |
| 23 | 25 | ||
| 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() | 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!!) | ||
| 29 | } | 34 | } |
| 35 | + | ||
| 36 | + override fun onReattachedToActivityForConfigChanges(binding: ActivityPluginBinding) { | ||
| 37 | + onAttachedToActivity(binding) | ||
| 30 | } | 38 | } |
| 31 | 39 | ||
| 32 | - override fun onDetachedFromEngine(@NonNull binding: FlutterPlugin.FlutterPluginBinding) { | ||
| 33 | - channel.setMethodCallHandler(null) | 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() | ||
| 34 | } | 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.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 = ( |
| @@ -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'), | 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 | + ), | ||
| 55 | ), | 63 | ), |
| 56 | - body: Center( | ||
| 57 | - child: Text('Running on: $_platformVersion\n'), | 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; | ||
| 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 | + } | ||
| 62 | } | 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 | ||
| 5 | + | ||
| 6 | +public class SwiftMobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHandler, FlutterTexture, AVCaptureVideoDataOutputSampleBufferDelegate { | ||
| 3 | 7 | ||
| 4 | -public class SwiftMobileScannerPlugin: NSObject, FlutterPlugin { | ||
| 5 | public static func register(with registrar: FlutterPluginRegistrar) { | 8 | 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 | + 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() | ||
| 9 | } | 32 | } |
| 10 | 33 | ||
| 11 | public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) { | 34 | public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) { |
| 12 | - result("iOS " + UIDevice.current.systemVersion) | 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 | + } | ||
| 13 | } | 200 | } |
| 14 | } | 201 | } |
ios/Classes/Util.swift
0 → 100644
| 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' |
lib/src/camera_args.dart
0 → 100644
lib/src/camera_controller.dart
0 → 100644
| 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 | +} |
lib/src/camera_facing.dart
0 → 100644
lib/src/camera_view.dart
0 → 100644
| 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,14 +21,44 @@ class Preview extends StatelessWidget { | @@ -18,14 +21,44 @@ 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); | ||
| 22 | 26 | ||
| 23 | @override | 27 | @override |
| 24 | - Widget build(BuildContext context) { | ||
| 25 | - return NativeDeviceOrientationReader( | ||
| 26 | - builder: (context) { | ||
| 27 | - var nativeOrientation = NativeDeviceOrientationReader.orientation(context); | 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 | + | ||
| 28 | 60 | ||
| 61 | + int _getRotationCompensation(NativeDeviceOrientation nativeOrientation) { | ||
| 29 | int nativeRotation = 0; | 62 | int nativeRotation = 0; |
| 30 | switch (nativeOrientation) { | 63 | switch (nativeOrientation) { |
| 31 | case NativeDeviceOrientation.portraitUp: | 64 | case NativeDeviceOrientation.portraitUp: |
| @@ -45,20 +78,46 @@ class Preview extends StatelessWidget { | @@ -45,20 +78,46 @@ class Preview extends StatelessWidget { | ||
| 45 | break; | 78 | break; |
| 46 | } | 79 | } |
| 47 | 80 | ||
| 48 | - int rotationCompensation = ((nativeRotation - sensorOrientation! + 450) % 360) ~/ 90; | 81 | + return ((nativeRotation - widget.sensorOrientation! + 450) % 360) ~/ 90; |
| 82 | + } | ||
| 83 | + | ||
| 84 | + @override | ||
| 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 | + | ||
| 104 | + return NativeDeviceOrientationReader( | ||
| 105 | + builder: (context) { | ||
| 106 | + var nativeOrientation = | ||
| 107 | + NativeDeviceOrientationReader.orientation(context); | ||
| 49 | 108 | ||
| 50 | - double frameHeight = width; | ||
| 51 | - double frameWidth = height; | 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 | ), |
lib/src/objects/barcode.dart
0 → 100644
| 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 | +} |
lib/src/torch_state.dart
0 → 100644
lib/src/util.dart
0 → 100644
| 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 | +} |
-
Please register or login to post a comment