Julian Steenbakker

feat: fix android scanner, torch, switch camera and much more

  1 +package dev.steenbakker.mobile_scanner
  2 +
  3 +import androidx.annotation.IntDef
  4 +
  5 +@IntDef(AnalyzeMode.NONE, AnalyzeMode.BARCODE)
  6 +@Target(AnnotationTarget.FIELD)
  7 +@Retention(AnnotationRetention.SOURCE)
  8 +annotation class AnalyzeMode {
  9 + companion object {
  10 + const val NONE = 0
  11 + const val BARCODE = 1
  12 + }
  13 +}
1 -package dev.steenbakker.mobile_scanner.old 1 +package dev.steenbakker.mobile_scanner
2 2
3 import com.google.mlkit.vision.barcode.BarcodeScannerOptions 3 import com.google.mlkit.vision.barcode.BarcodeScannerOptions
4 import java.util.ArrayList 4 import java.util.ArrayList
@@ -81,12 +81,12 @@ enum class BarcodeFormats(val intValue: Int) { @@ -81,12 +81,12 @@ enum class BarcodeFormats(val intValue: Int) {
81 81
82 init { 82 init {
83 val values = values() 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 -// } 84 + formatsMap =
  85 + HashMap<String, Int>(values.size * 4 / 3)
  86 + for (value in values) {
  87 + formatsMap!![value.name] =
  88 + value.intValue
  89 + }
90 } 90 }
91 } 91 }
92 } 92 }
1 package dev.steenbakker.mobile_scanner 1 package dev.steenbakker.mobile_scanner
2 2
3 import android.Manifest 3 import android.Manifest
4 -import android.R.attr.height  
5 -import android.R.attr.width  
6 -import android.annotation.SuppressLint  
7 import android.app.Activity 4 import android.app.Activity
8 -import android.content.Context  
9 import android.content.pm.PackageManager 5 import android.content.pm.PackageManager
10 -import android.graphics.ImageFormat  
11 -import android.graphics.SurfaceTexture  
12 -import android.hardware.camera2.CameraCharacteristics  
13 -import android.hardware.camera2.CameraManager  
14 -import android.hardware.camera2.params.StreamConfigurationMap 6 +import android.graphics.Point
15 import android.util.Log 7 import android.util.Log
16 -import android.util.Rational  
17 import android.util.Size 8 import android.util.Size
18 import android.view.Surface 9 import android.view.Surface
19 -import android.view.Surface.ROTATION_0  
20 -import android.view.Surface.ROTATION_180  
21 -import androidx.annotation.IntDef  
22 import androidx.annotation.NonNull 10 import androidx.annotation.NonNull
23 import androidx.camera.core.* 11 import androidx.camera.core.*
24 -import androidx.camera.core.impl.PreviewConfig  
25 import androidx.camera.lifecycle.ProcessCameraProvider 12 import androidx.camera.lifecycle.ProcessCameraProvider
26 import androidx.core.app.ActivityCompat 13 import androidx.core.app.ActivityCompat
27 import androidx.core.content.ContextCompat 14 import androidx.core.content.ContextCompat
28 import androidx.lifecycle.LifecycleOwner 15 import androidx.lifecycle.LifecycleOwner
  16 +import com.google.mlkit.vision.barcode.BarcodeScannerOptions
29 import com.google.mlkit.vision.barcode.BarcodeScanning 17 import com.google.mlkit.vision.barcode.BarcodeScanning
  18 +import com.google.mlkit.vision.barcode.common.Barcode
30 import com.google.mlkit.vision.common.InputImage 19 import com.google.mlkit.vision.common.InputImage
31 import io.flutter.plugin.common.EventChannel 20 import io.flutter.plugin.common.EventChannel
32 import io.flutter.plugin.common.MethodCall 21 import io.flutter.plugin.common.MethodCall
@@ -38,7 +27,8 @@ import io.flutter.view.TextureRegistry @@ -38,7 +27,8 @@ import io.flutter.view.TextureRegistry
38 class MobileScanner(private val activity: Activity, private val textureRegistry: TextureRegistry) 27 class MobileScanner(private val activity: Activity, private val textureRegistry: TextureRegistry)
39 : MethodChannel.MethodCallHandler, EventChannel.StreamHandler, PluginRegistry.RequestPermissionsResultListener { 28 : MethodChannel.MethodCallHandler, EventChannel.StreamHandler, PluginRegistry.RequestPermissionsResultListener {
40 companion object { 29 companion object {
41 - private const val REQUEST_CODE = 19930430 30 + private const val REQUEST_CODE = 22022022
  31 + private val TAG = MobileScanner::class.java.simpleName
42 } 32 }
43 33
44 private var sink: EventChannel.EventSink? = null 34 private var sink: EventChannel.EventSink? = null
@@ -54,12 +44,12 @@ class MobileScanner(private val activity: Activity, private val textureRegistry: @@ -54,12 +44,12 @@ class MobileScanner(private val activity: Activity, private val textureRegistry:
54 @ExperimentalGetImage 44 @ExperimentalGetImage
55 override fun onMethodCall(@NonNull call: MethodCall, @NonNull result: MethodChannel.Result) { 45 override fun onMethodCall(@NonNull call: MethodCall, @NonNull result: MethodChannel.Result) {
56 when (call.method) { 46 when (call.method) {
57 - "state" -> stateNative(result)  
58 - "request" -> requestNative(result)  
59 - "start" -> startNative(call, result)  
60 - "torch" -> torchNative(call, result)  
61 - "analyze" -> analyzeNative(call, result)  
62 - "stop" -> stopNative(result) 47 + "state" -> checkPermission(result)
  48 + "request" -> requestPermission(result)
  49 + "start" -> start(call, result)
  50 + "torch" -> switchTorch(call, result)
  51 + "analyze" -> switchAnalyzeMode(call, result)
  52 + "stop" -> stop(result)
63 else -> result.notImplemented() 53 else -> result.notImplemented()
64 } 54 }
65 } 55 }
@@ -76,7 +66,7 @@ class MobileScanner(private val activity: Activity, private val textureRegistry: @@ -76,7 +66,7 @@ class MobileScanner(private val activity: Activity, private val textureRegistry:
76 return listener?.onRequestPermissionsResult(requestCode, permissions, grantResults) ?: false 66 return listener?.onRequestPermissionsResult(requestCode, permissions, grantResults) ?: false
77 } 67 }
78 68
79 - private fun stateNative(result: MethodChannel.Result) { 69 + private fun checkPermission(result: MethodChannel.Result) {
80 // Can't get exact denied or not_determined state without request. Just return not_determined when state isn't authorized 70 // Can't get exact denied or not_determined state without request. Just return not_determined when state isn't authorized
81 val state = 71 val state =
82 if (ContextCompat.checkSelfPermission(activity, Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED) 1 72 if (ContextCompat.checkSelfPermission(activity, Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED) 1
@@ -84,7 +74,7 @@ class MobileScanner(private val activity: Activity, private val textureRegistry: @@ -84,7 +74,7 @@ class MobileScanner(private val activity: Activity, private val textureRegistry:
84 result.success(state) 74 result.success(state)
85 } 75 }
86 76
87 - private fun requestNative(result: MethodChannel.Result) { 77 + private fun requestPermission(result: MethodChannel.Result) {
88 listener = PluginRegistry.RequestPermissionsResultListener { requestCode, _, grantResults -> 78 listener = PluginRegistry.RequestPermissionsResultListener { requestCode, _, grantResults ->
89 if (requestCode != REQUEST_CODE) { 79 if (requestCode != REQUEST_CODE) {
90 false 80 false
@@ -99,113 +89,115 @@ class MobileScanner(private val activity: Activity, private val textureRegistry: @@ -99,113 +89,115 @@ class MobileScanner(private val activity: Activity, private val textureRegistry:
99 ActivityCompat.requestPermissions(activity, permissions, REQUEST_CODE) 89 ActivityCompat.requestPermissions(activity, permissions, REQUEST_CODE)
100 } 90 }
101 91
102 - private var sensorOrientation = 0  
103 92
104 @ExperimentalGetImage 93 @ExperimentalGetImage
105 -// @androidx.camera.camera2.interop.ExperimentalCamera2Interop  
106 - private fun startNative(call: MethodCall, result: MethodChannel.Result) { 94 + val analyzer = ImageAnalysis.Analyzer { imageProxy -> // YUV_420_888 format
  95 + when (analyzeMode) {
  96 + AnalyzeMode.BARCODE -> {
  97 + val mediaImage = imageProxy.image ?: return@Analyzer
  98 + val inputImage = InputImage.fromMediaImage(mediaImage, imageProxy.imageInfo.rotationDegrees)
107 99
108 - val targetWidth: Int? = call.argument<Int>("targetWidth")  
109 - val targetHeight: Int? = call.argument<Int>("targetHeight")  
110 - val facing: Int? = call.argument<Int>("facing")  
111 -  
112 - if (targetWidth == null || targetHeight == null) {  
113 - result.error("INVALID_ARGUMENT", "Missing a required argument", "Expecting targetWidth, targetHeight")  
114 - return 100 + scanner.process(inputImage)
  101 + .addOnSuccessListener { barcodes ->
  102 + for (barcode in barcodes) {
  103 + val event = mapOf("name" to "barcode", "data" to barcode.data)
  104 + sink?.success(event)
  105 + }
  106 + }
  107 + .addOnFailureListener { e -> Log.e(TAG, e.message, e) }
  108 + .addOnCompleteListener { imageProxy.close() }
  109 + }
  110 + else -> imageProxy.close()
115 } 111 }
  112 + }
  113 +
  114 +
  115 + private var scanner = BarcodeScanning.getClient()
116 116
117 - Log.i("LOG", "Target resolution : $targetWidth, $targetHeight") 117 + @ExperimentalGetImage
  118 + private fun start(call: MethodCall, result: MethodChannel.Result) {
  119 +
  120 + val facing: Int = call.argument<Int>("facing") ?: 0
  121 + val ratio: Int? = call.argument<Int>("ratio")
  122 + val torch: Boolean = call.argument<Boolean>("torch") ?: false
  123 + val formatStrings: List<String>? = call.argument<List<String>>("formats")
  124 +
  125 + if (formatStrings != null) {
  126 + val options: BarcodeScannerOptions = BarcodeFormats.optionsFromStringList(formatStrings)
  127 + scanner = BarcodeScanning.getClient(options)
  128 + }
118 129
119 val future = ProcessCameraProvider.getInstance(activity) 130 val future = ProcessCameraProvider.getInstance(activity)
120 val executor = ContextCompat.getMainExecutor(activity) 131 val executor = ContextCompat.getMainExecutor(activity)
  132 +
121 future.addListener({ 133 future.addListener({
122 cameraProvider = future.get() 134 cameraProvider = future.get()
123 textureEntry = textureRegistry.createSurfaceTexture() 135 textureEntry = textureRegistry.createSurfaceTexture()
124 - val textureId = textureEntry!!.id() 136 +
125 // Preview 137 // Preview
126 val surfaceProvider = Preview.SurfaceProvider { request -> 138 val surfaceProvider = Preview.SurfaceProvider { request ->
127 val texture = textureEntry!!.surfaceTexture() 139 val texture = textureEntry!!.surfaceTexture()
128 - val resolution = request.resolution  
129 - texture.setDefaultBufferSize(resolution.width, resolution.height)  
130 - Log.i("LOG", "Image resolution : ${request.resolution}") 140 + texture.setDefaultBufferSize(request.resolution.width, request.resolution.height)
131 val surface = Surface(texture) 141 val surface = Surface(texture)
132 request.provideSurface(surface, executor) { } 142 request.provideSurface(surface, executor) { }
133 } 143 }
134 -// PreviewConfig().apply { }  
135 -// val previewConfig = PreviewConfig.Builder().apply {  
136 -// setTargetAspectRatio(SQUARE_ASPECT_RATIO)  
137 -// setTargetRotation(viewFinder.display.rotation)  
138 -// }.build()  
139 -  
140 -  
141 - val preview = Preview.Builder()  
142 - .setTargetResolution(Size(targetWidth, targetHeight))  
143 - .build().apply { setSurfaceProvider(surfaceProvider) }  
144 - // Analyzer  
145 - val analyzer = ImageAnalysis.Analyzer { imageProxy -> // YUV_420_888 format  
146 - when (analyzeMode) {  
147 - AnalyzeMode.BARCODE -> {  
148 - val mediaImage = imageProxy.image ?: return@Analyzer  
149 - val inputImage = InputImage.fromMediaImage(mediaImage, imageProxy.imageInfo.rotationDegrees)  
150 - val scanner = BarcodeScanning.getClient()  
151 - scanner.process(inputImage)  
152 - .addOnSuccessListener { barcodes ->  
153 - for (barcode in barcodes) {  
154 - val event = mapOf("name" to "barcode", "data" to barcode.data)  
155 - sink?.success(event)  
156 - }  
157 - }  
158 - .addOnFailureListener { e -> Log.e(TAG, e.message, e) }  
159 - .addOnCompleteListener { imageProxy.close() }  
160 - }  
161 - else -> imageProxy.close()  
162 - } 144 +
  145 + // Build the preview to be shown on the Flutter texture
  146 + val previewBuilder = Preview.Builder()
  147 + if (ratio != null) {
  148 + previewBuilder.setTargetAspectRatio(ratio)
163 } 149 }
164 - val analysis = ImageAnalysis.Builder() 150 + val preview = previewBuilder.build().apply { setSurfaceProvider(surfaceProvider) }
  151 +
  152 + // Build the analyzer to be passed on to MLKit
  153 + val analysisBuilder = ImageAnalysis.Builder()
165 .setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST) 154 .setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
166 - .setTargetResolution(Size(targetWidth, targetHeight))  
167 - .build().apply { setAnalyzer(executor, analyzer) }  
168 - // Bind to lifecycle.  
169 - val owner = activity as LifecycleOwner  
170 - val selector =  
171 - if (call.arguments == 0) CameraSelector.DEFAULT_FRONT_CAMERA  
172 - else CameraSelector.DEFAULT_BACK_CAMERA  
173 - camera = cameraProvider!!.bindToLifecycle(owner, selector, preview, analysis)  
174 -  
175 - val analysisSize = analysis.resolutionInfo?.resolution ?: Size(0, 0)  
176 - val previewSize = preview.resolutionInfo?.resolution ?: Size(0, 0)  
177 - Log.i("LOG", "Analyzer: $analysisSize")  
178 - Log.i("LOG", "Preview: $previewSize")  
179 -  
180 - camera!!.cameraInfo.torchState.observe(owner) { state -> 155 + if (ratio != null) {
  156 + analysisBuilder.setTargetAspectRatio(ratio)
  157 + }
  158 + val analysis = analysisBuilder.build().apply { setAnalyzer(executor, analyzer) }
  159 +
  160 + // Select the correct camera
  161 + val selector = if (facing == 0) CameraSelector.DEFAULT_FRONT_CAMERA else CameraSelector.DEFAULT_BACK_CAMERA
  162 +
  163 + camera = cameraProvider!!.bindToLifecycle(activity as LifecycleOwner, selector, preview, analysis)
  164 +
  165 + val analysisSize = analysis.resolutionInfo?.resolution ?: Size(0, 0)
  166 + val previewSize = preview.resolutionInfo?.resolution ?: Size(0, 0)
  167 + Log.i("LOG", "Analyzer: $analysisSize")
  168 + Log.i("LOG", "Preview: $previewSize")
  169 +
  170 + // Register the torch listener
  171 + camera!!.cameraInfo.torchState.observe(activity) { state ->
181 // TorchState.OFF = 0; TorchState.ON = 1 172 // TorchState.OFF = 0; TorchState.ON = 1
182 - val event = mapOf("name" to "torchState", "data" to state)  
183 - sink?.success(event) 173 + sink?.success(mapOf("name" to "torchState", "data" to state))
184 } 174 }
185 175
  176 + // Enable torch if provided
  177 + camera!!.cameraControl.enableTorch(torch)
  178 +
186 val resolution = preview.resolutionInfo!!.resolution 179 val resolution = preview.resolutionInfo!!.resolution
187 val portrait = camera!!.cameraInfo.sensorRotationDegrees % 180 == 0 180 val portrait = camera!!.cameraInfo.sensorRotationDegrees % 180 == 0
188 val width = resolution.width.toDouble() 181 val width = resolution.width.toDouble()
189 val height = resolution.height.toDouble() 182 val height = resolution.height.toDouble()
190 -// val size = mapOf("width" to 1920.0, "height" to 1080.0)  
191 val size = if (portrait) mapOf("width" to width, "height" to height) else mapOf("width" to height, "height" to width) 183 val size = if (portrait) mapOf("width" to width, "height" to height) else mapOf("width" to height, "height" to width)
192 - val answer = mapOf("textureId" to textureId, "size" to size, "torchable" to camera!!.torchable) 184 + val answer = mapOf("textureId" to textureEntry!!.id(), "size" to size, "torchable" to camera!!.cameraInfo.hasFlashUnit())
193 result.success(answer) 185 result.success(answer)
194 }, executor) 186 }, executor)
195 } 187 }
196 188
197 - private fun torchNative(call: MethodCall, result: MethodChannel.Result) { 189 + private fun switchTorch(call: MethodCall, result: MethodChannel.Result) {
198 val state = call.arguments == 1 190 val state = call.arguments == 1
199 camera!!.cameraControl.enableTorch(state) 191 camera!!.cameraControl.enableTorch(state)
200 result.success(null) 192 result.success(null)
201 } 193 }
202 194
203 - private fun analyzeNative(call: MethodCall, result: MethodChannel.Result) { 195 + private fun switchAnalyzeMode(call: MethodCall, result: MethodChannel.Result) {
204 analyzeMode = call.arguments as Int 196 analyzeMode = call.arguments as Int
205 result.success(null) 197 result.success(null)
206 } 198 }
207 199
208 - private fun stopNative(result: MethodChannel.Result) { 200 + private fun stop(result: MethodChannel.Result) {
209 val owner = activity as LifecycleOwner 201 val owner = activity as LifecycleOwner
210 camera!!.cameraInfo.torchState.removeObservers(owner) 202 camera!!.cameraInfo.torchState.removeObservers(owner)
211 cameraProvider!!.unbindAll() 203 cameraProvider!!.unbindAll()
@@ -218,14 +210,60 @@ class MobileScanner(private val activity: Activity, private val textureRegistry: @@ -218,14 +210,60 @@ class MobileScanner(private val activity: Activity, private val textureRegistry:
218 210
219 result.success(null) 211 result.success(null)
220 } 212 }
221 -}  
222 213
223 -@IntDef(AnalyzeMode.NONE, AnalyzeMode.BARCODE)  
224 -@Target(AnnotationTarget.FIELD)  
225 -@Retention(AnnotationRetention.SOURCE)  
226 -annotation class AnalyzeMode {  
227 - companion object {  
228 - const val NONE = 0  
229 - const val BARCODE = 1  
230 - } 214 +
  215 + private val Barcode.data: Map<String, Any?>
  216 + get() = mapOf("corners" to cornerPoints?.map { corner -> corner.data }, "format" to format,
  217 + "rawBytes" to rawBytes, "rawValue" to rawValue, "type" to valueType,
  218 + "calendarEvent" to calendarEvent?.data, "contactInfo" to contactInfo?.data,
  219 + "driverLicense" to driverLicense?.data, "email" to email?.data,
  220 + "geoPoint" to geoPoint?.data, "phone" to phone?.data, "sms" to sms?.data,
  221 + "url" to url?.data, "wifi" to wifi?.data)
  222 +
  223 + private val Point.data: Map<String, Double>
  224 + get() = mapOf("x" to x.toDouble(), "y" to y.toDouble())
  225 +
  226 + private val Barcode.CalendarEvent.data: Map<String, Any?>
  227 + get() = mapOf("description" to description, "end" to end?.rawValue, "location" to location,
  228 + "organizer" to organizer, "start" to start?.rawValue, "status" to status,
  229 + "summary" to summary)
  230 +
  231 + private val Barcode.ContactInfo.data: Map<String, Any?>
  232 + get() = mapOf("addresses" to addresses.map { address -> address.data },
  233 + "emails" to emails.map { email -> email.data }, "name" to name?.data,
  234 + "organization" to organization, "phones" to phones.map { phone -> phone.data },
  235 + "title" to title, "urls" to urls)
  236 +
  237 + private val Barcode.Address.data: Map<String, Any?>
  238 + get() = mapOf("addressLines" to addressLines, "type" to type)
  239 +
  240 + private val Barcode.PersonName.data: Map<String, Any?>
  241 + get() = mapOf("first" to first, "formattedName" to formattedName, "last" to last,
  242 + "middle" to middle, "prefix" to prefix, "pronunciation" to pronunciation,
  243 + "suffix" to suffix)
  244 +
  245 + private val Barcode.DriverLicense.data: Map<String, Any?>
  246 + get() = mapOf("addressCity" to addressCity, "addressState" to addressState,
  247 + "addressStreet" to addressStreet, "addressZip" to addressZip, "birthDate" to birthDate,
  248 + "documentType" to documentType, "expiryDate" to expiryDate, "firstName" to firstName,
  249 + "gender" to gender, "issueDate" to issueDate, "issuingCountry" to issuingCountry,
  250 + "lastName" to lastName, "licenseNumber" to licenseNumber, "middleName" to middleName)
  251 +
  252 + private val Barcode.Email.data: Map<String, Any?>
  253 + get() = mapOf("address" to address, "body" to body, "subject" to subject, "type" to type)
  254 +
  255 + private val Barcode.GeoPoint.data: Map<String, Any?>
  256 + get() = mapOf("latitude" to lat, "longitude" to lng)
  257 +
  258 + private val Barcode.Phone.data: Map<String, Any?>
  259 + get() = mapOf("number" to number, "type" to type)
  260 +
  261 + private val Barcode.Sms.data: Map<String, Any?>
  262 + get() = mapOf("message" to message, "phoneNumber" to phoneNumber)
  263 +
  264 + private val Barcode.UrlBookmark.data: Map<String, Any?>
  265 + get() = mapOf("title" to title, "url" to url)
  266 +
  267 + private val Barcode.WiFi.data: Map<String, Any?>
  268 + get() = mapOf("encryptionType" to encryptionType, "password" to password, "ssid" to ssid)
231 } 269 }
@@ -7,7 +7,7 @@ import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding @@ -7,7 +7,7 @@ import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding
7 import io.flutter.plugin.common.EventChannel 7 import io.flutter.plugin.common.EventChannel
8 import io.flutter.plugin.common.MethodChannel 8 import io.flutter.plugin.common.MethodChannel
9 9
10 -/** CameraXPlugin */ 10 +/** MobileScannerPlugin */
11 class MobileScannerPlugin : FlutterPlugin, ActivityAware { 11 class MobileScannerPlugin : FlutterPlugin, ActivityAware {
12 private var flutter: FlutterPlugin.FlutterPluginBinding? = null 12 private var flutter: FlutterPlugin.FlutterPluginBinding? = null
13 private var activity: ActivityPluginBinding? = null 13 private var activity: ActivityPluginBinding? = null
1 -package dev.steenbakker.mobile_scanner  
2 -  
3 -import android.graphics.Point  
4 -import androidx.camera.core.Camera  
5 -import androidx.camera.core.ImageProxy  
6 -import com.google.mlkit.vision.barcode.common.Barcode  
7 -  
8 -val Any.TAG: String  
9 - get() = javaClass.simpleName  
10 -  
11 -val Camera.torchable: Boolean  
12 - get() = cameraInfo.hasFlashUnit()  
13 -//  
14 -//val ImageProxy.yuv: ByteArray  
15 -// get() {  
16 -// val ySize = y.buffer.remaining()  
17 -// val uSize = u.buffer.remaining()  
18 -// val vSize = v.buffer.remaining()  
19 -//  
20 -// val size = ySize + uSize + vSize  
21 -// val data = ByteArray(size)  
22 -//  
23 -// var offset = 0  
24 -// y.buffer.get(data, offset, ySize)  
25 -// offset += ySize  
26 -// u.buffer.get(data, offset, uSize)  
27 -// offset += uSize  
28 -// v.buffer.get(data, offset, vSize)  
29 -//  
30 -// return data  
31 -// }  
32 -//  
33 -//val ImageProxy.nv21: ByteArray  
34 -// get() {  
35 -// if (BuildConfig.DEBUG) {  
36 -// if (y.pixelStride != 1 || u.rowStride != v.rowStride || u.pixelStride != v.pixelStride) {  
37 -// error("Assertion failed")  
38 -// }  
39 -// }  
40 -//  
41 -// val ySize = width * height  
42 -// val uvSize = ySize / 2  
43 -// val size = ySize + uvSize  
44 -// val data = ByteArray(size)  
45 -//  
46 -// var offset = 0  
47 -// // Y Plane  
48 -// if (y.rowStride == width) {  
49 -// y.buffer.get(data, offset, ySize)  
50 -// offset += ySize  
51 -// } else {  
52 -// for (row in 0 until height) {  
53 -// y.buffer.get(data, offset, width)  
54 -// offset += width  
55 -// }  
56 -//  
57 -// if (BuildConfig.DEBUG && offset != ySize) {  
58 -// error("Assertion failed")  
59 -// }  
60 -// }  
61 -// // U,V Planes  
62 -// if (v.rowStride == width && v.pixelStride == 2) {  
63 -// if (BuildConfig.DEBUG && v.size != uvSize - 1) {  
64 -// error("Assertion failed")  
65 -// }  
66 -//  
67 -// v.buffer.get(data, offset, 1)  
68 -// offset += 1  
69 -// u.buffer.get(data, offset, u.size)  
70 -// if (BuildConfig.DEBUG) {  
71 -// val value = v.buffer.get()  
72 -// if (data[offset] != value) {  
73 -// error("Assertion failed")  
74 -// }  
75 -// }  
76 -// } else {  
77 -// for (row in 0 until height / 2)  
78 -// for (col in 0 until width / 2) {  
79 -// val index = row * v.rowStride + col * v.pixelStride  
80 -// data[offset++] = v.buffer.get(index)  
81 -// data[offset++] = u.buffer.get(index)  
82 -// }  
83 -//  
84 -// if (BuildConfig.DEBUG && offset != size) {  
85 -// error("Assertion failed")  
86 -// }  
87 -// }  
88 -//  
89 -// return data  
90 -// }  
91 -  
92 -val ImageProxy.PlaneProxy.size  
93 - get() = buffer.remaining()  
94 -  
95 -val ImageProxy.y: ImageProxy.PlaneProxy  
96 - get() = planes[0]  
97 -  
98 -val ImageProxy.u: ImageProxy.PlaneProxy  
99 - get() = planes[1]  
100 -  
101 -val ImageProxy.v: ImageProxy.PlaneProxy  
102 - get() = planes[2]  
103 -  
104 -val Barcode.data: Map<String, Any?>  
105 - get() = mapOf("corners" to cornerPoints?.map { corner -> corner.data }, "format" to format,  
106 - "rawBytes" to rawBytes, "rawValue" to rawValue, "type" to valueType,  
107 - "calendarEvent" to calendarEvent?.data, "contactInfo" to contactInfo?.data,  
108 - "driverLicense" to driverLicense?.data, "email" to email?.data,  
109 - "geoPoint" to geoPoint?.data, "phone" to phone?.data, "sms" to sms?.data,  
110 - "url" to url?.data, "wifi" to wifi?.data)  
111 -  
112 -val Point.data: Map<String, Double>  
113 - get() = mapOf("x" to x.toDouble(), "y" to y.toDouble())  
114 -  
115 -val Barcode.CalendarEvent.data: Map<String, Any?>  
116 - get() = mapOf("description" to description, "end" to end?.rawValue, "location" to location,  
117 - "organizer" to organizer, "start" to start?.rawValue, "status" to status,  
118 - "summary" to summary)  
119 -  
120 -val Barcode.ContactInfo.data: Map<String, Any?>  
121 - get() = mapOf("addresses" to addresses.map { address -> address.data },  
122 - "emails" to emails.map { email -> email.data }, "name" to name?.data,  
123 - "organization" to organization, "phones" to phones.map { phone -> phone.data },  
124 - "title" to title, "urls" to urls)  
125 -  
126 -val Barcode.Address.data: Map<String, Any?>  
127 - get() = mapOf("addressLines" to addressLines, "type" to type)  
128 -  
129 -val Barcode.PersonName.data: Map<String, Any?>  
130 - get() = mapOf("first" to first, "formattedName" to formattedName, "last" to last,  
131 - "middle" to middle, "prefix" to prefix, "pronunciation" to pronunciation,  
132 - "suffix" to suffix)  
133 -  
134 -val Barcode.DriverLicense.data: Map<String, Any?>  
135 - get() = mapOf("addressCity" to addressCity, "addressState" to addressState,  
136 - "addressStreet" to addressStreet, "addressZip" to addressZip, "birthDate" to birthDate,  
137 - "documentType" to documentType, "expiryDate" to expiryDate, "firstName" to firstName,  
138 - "gender" to gender, "issueDate" to issueDate, "issuingCountry" to issuingCountry,  
139 - "lastName" to lastName, "licenseNumber" to licenseNumber, "middleName" to middleName)  
140 -  
141 -val Barcode.Email.data: Map<String, Any?>  
142 - get() = mapOf("address" to address, "body" to body, "subject" to subject, "type" to type)  
143 -  
144 -val Barcode.GeoPoint.data: Map<String, Any?>  
145 - get() = mapOf("latitude" to lat, "longitude" to lng)  
146 -  
147 -val Barcode.Phone.data: Map<String, Any?>  
148 - get() = mapOf("number" to number, "type" to type)  
149 -  
150 -val Barcode.Sms.data: Map<String, Any?>  
151 - get() = mapOf("message" to message, "phoneNumber" to phoneNumber)  
152 -  
153 -val Barcode.UrlBookmark.data: Map<String, Any?>  
154 - get() = mapOf("title" to title, "url" to url)  
155 -  
156 -val Barcode.WiFi.data: Map<String, Any?>  
157 - get() = mapOf("encryptionType" to encryptionType, "password" to password, "ssid" to ssid)  
1 package dev.steenbakker.mobile_scanner.exceptions 1 package dev.steenbakker.mobile_scanner.exceptions
2 2
3 -internal class NoPermissionException : RuntimeException()  
  3 +internal class NoPermissionException : RuntimeException()
  4 +
  5 +//internal class Exception(val reason: Reason) :
  6 +// java.lang.Exception("Mobile Scanner failed because $reason") {
  7 +//
  8 +// internal enum class Reason {
  9 +// noHardware, noPermissions, noBackCamera
  10 +// }
  11 +//}
1 -package dev.steenbakker.mobile_scanner.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 -}  
@@ -16,57 +16,95 @@ class AnalyzeView extends StatefulWidget { @@ -16,57 +16,95 @@ class AnalyzeView extends StatefulWidget {
16 16
17 class _AnalyzeViewState extends State<AnalyzeView> 17 class _AnalyzeViewState extends State<AnalyzeView>
18 with SingleTickerProviderStateMixin { 18 with SingleTickerProviderStateMixin {
19 - List<Offset> points = []; 19 + String? barcode;
20 20
21 - // CameraController cameraController = CameraController(context, width: 320, height: 150);  
22 -  
23 - String? barcode = null; 21 + MobileScannerController controller = MobileScannerController(torchEnabled: true,
  22 + facing: CameraFacing.front,);
24 23
25 @override 24 @override
26 Widget build(BuildContext context) { 25 Widget build(BuildContext context) {
27 return MaterialApp( 26 return MaterialApp(
28 home: Scaffold( 27 home: Scaffold(
  28 + backgroundColor: Colors.black,
29 body: Builder(builder: (context) { 29 body: Builder(builder: (context) {
30 return Stack( 30 return Stack(
31 children: [ 31 children: [
32 MobileScanner( 32 MobileScanner(
33 - // fitScreen: false,  
34 - // controller: cameraController, 33 + controller: controller,
  34 + fit: BoxFit.contain,
  35 + // controller: MobileScannerController(
  36 + // torchEnabled: true,
  37 + // facing: CameraFacing.front,
  38 + // ),
35 onDetect: (barcode, args) { 39 onDetect: (barcode, args) {
36 if (this.barcode != barcode.rawValue) { 40 if (this.barcode != barcode.rawValue) {
37 - this.barcode = barcode.rawValue;  
38 - if (barcode.corners != null) {  
39 - ScaffoldMessenger.of(context).showSnackBar(SnackBar(  
40 - content: Text('${barcode.rawValue}'),  
41 - duration: const Duration(milliseconds: 200),  
42 - animation: null,  
43 - ));  
44 - setState(() {  
45 - final List<Offset> points = [];  
46 - double factorWidth = args.size.width / 520;  
47 - // double factorHeight = wanted / args.size.height;  
48 - final size = MediaQuery.of(context).devicePixelRatio;  
49 - debugPrint('Size: ${barcode.corners}');  
50 - for (var point in barcode.corners!) {  
51 - final adjustedWith = point.dx ;  
52 - final adjustedHeight= point.dy ;  
53 - points.add(Offset(adjustedWith / size, adjustedHeight / size));  
54 - // points.add(Offset((point.dx ) / size,  
55 - // (point.dy) / size));  
56 - // final differenceWidth = (args.wantedSize!.width - args.size.width) / 2;  
57 - // final differenceHeight = (args.wantedSize!.height - args.size.height) / 2;  
58 - // points.add(Offset((point.dx + differenceWidth) / size,  
59 - // (point.dy + differenceHeight) / size));  
60 - }  
61 - this.points = points;  
62 - });  
63 - } 41 + setState(() {
  42 + this.barcode = barcode.rawValue;
  43 + });
64 } 44 }
65 - // Default 640 x480  
66 }), 45 }),
67 - CustomPaint(  
68 - painter: OpenPainter(points), 46 + Align(
  47 + alignment: Alignment.bottomCenter,
  48 + child: Container(
  49 + alignment: Alignment.bottomCenter,
  50 + height: 100,
  51 + color: Colors.black.withOpacity(0.4),
  52 + child: Row(
  53 + crossAxisAlignment: CrossAxisAlignment.center,
  54 + mainAxisAlignment: MainAxisAlignment.spaceEvenly,
  55 + children: [
  56 + IconButton(
  57 + color: Colors.white,
  58 + icon: ValueListenableBuilder(
  59 + valueListenable: controller.torchState,
  60 + builder: (context, state, child) {
  61 + switch (state as TorchState) {
  62 + case TorchState.off:
  63 + return const Icon(Icons.flash_off, color: Colors.grey);
  64 + case TorchState.on:
  65 + return const Icon(Icons.flash_on, color: Colors.yellow);
  66 + }
  67 + },
  68 + ),
  69 + iconSize: 32.0,
  70 + onPressed: () => controller.toggleTorch(),
  71 + ),
  72 + Center(
  73 + child: SizedBox(
  74 + width: MediaQuery.of(context).size.width - 120,
  75 + height: 50,
  76 + child: FittedBox(
  77 + child: Text(
  78 + barcode ?? 'Scan something!',
  79 + overflow: TextOverflow.fade,
  80 + style: Theme.of(context)
  81 + .textTheme
  82 + .headline4!
  83 + .copyWith(color: Colors.white),
  84 + ),
  85 + ),
  86 + ),
  87 + ),
  88 + IconButton(
  89 + color: Colors.white,
  90 + icon: ValueListenableBuilder(
  91 + valueListenable: controller.cameraFacingState,
  92 + builder: (context, state, child) {
  93 + if (state == CameraFacing.front) {
  94 + return const Icon(Icons.camera_front);
  95 + } else {
  96 + return const Icon(Icons.camera_rear);
  97 + }
  98 + },
  99 + ),
  100 + iconSize: 32.0,
  101 + onPressed: () => controller.switchCamera(),
  102 + ),
  103 + ],
  104 + ),
  105 + ),
69 ), 106 ),
  107 +
70 // Container( 108 // Container(
71 // alignment: Alignment.bottomCenter, 109 // alignment: Alignment.bottomCenter,
72 // margin: EdgeInsets.only(bottom: 80.0), 110 // margin: EdgeInsets.only(bottom: 80.0),
@@ -101,36 +139,6 @@ class _AnalyzeViewState extends State<AnalyzeView> @@ -101,36 +139,6 @@ class _AnalyzeViewState extends State<AnalyzeView>
101 } 139 }
102 } 140 }
103 141
104 -class OpenPainter extends CustomPainter {  
105 - final List<Offset> points;  
106 -  
107 - OpenPainter(this.points);  
108 - @override  
109 - void paint(Canvas canvas, Size size) {  
110 - var paint1 = Paint()  
111 - ..color = Color(0xff63aa65)  
112 - ..strokeWidth = 10;  
113 - //draw points on canvas  
114 - canvas.drawPoints(PointMode.points, points, paint1);  
115 - }  
116 -  
117 - @override  
118 - bool shouldRepaint(CustomPainter oldDelegate) => true;  
119 -}  
120 -  
121 -class OpacityCurve extends Curve {  
122 - @override  
123 - double transform(double t) {  
124 - if (t < 0.1) {  
125 - return t * 10;  
126 - } else if (t <= 0.9) {  
127 - return 1.0;  
128 - } else {  
129 - return (1.0 - t) * 10;  
130 - }  
131 - }  
132 -}  
133 -  
134 // import 'package:flutter/material.dart'; 142 // import 'package:flutter/material.dart';
135 // import 'package:flutter/rendering.dart'; 143 // import 'package:flutter/rendering.dart';
136 // import 'package:mobile_scanner/mobile_scanner.dart'; 144 // import 'package:mobile_scanner/mobile_scanner.dart';
  1 +import 'dart:ui';
  2 +
  3 +import 'package:flutter/material.dart';
  4 +import 'package:mobile_scanner/mobile_scanner.dart';
  5 +
  6 +void main() {
  7 + runApp(const AnalyzeView());
  8 +}
  9 +
  10 +class AnalyzeView extends StatefulWidget {
  11 + const AnalyzeView({Key? key}) : super(key: key);
  12 +
  13 + @override
  14 + _AnalyzeViewState createState() => _AnalyzeViewState();
  15 +}
  16 +
  17 +class _AnalyzeViewState extends State<AnalyzeView>
  18 + with SingleTickerProviderStateMixin {
  19 + List<Offset> points = [];
  20 +
  21 + // CameraController cameraController = CameraController(context, width: 320, height: 150);
  22 +
  23 + String? barcode = null;
  24 +
  25 + @override
  26 + Widget build(BuildContext context) {
  27 + return MaterialApp(
  28 + home: Scaffold(
  29 + body: Builder(builder: (context) {
  30 + return Stack(
  31 + children: [
  32 + MobileScanner(
  33 + // fitScreen: false,
  34 + // controller: cameraController,
  35 + onDetect: (barcode, args) {
  36 + if (this.barcode != barcode.rawValue) {
  37 + this.barcode = barcode.rawValue;
  38 + if (barcode.corners != null) {
  39 + ScaffoldMessenger.of(context).showSnackBar(SnackBar(
  40 + content: Text('${barcode.rawValue}'),
  41 + duration: const Duration(milliseconds: 200),
  42 + animation: null,
  43 + ));
  44 + setState(() {
  45 + final List<Offset> points = [];
  46 + double factorWidth = args.size.width / 520;
  47 + // double factorHeight = wanted / args.size.height;
  48 + final size = MediaQuery.of(context).devicePixelRatio;
  49 + debugPrint('Size: ${barcode.corners}');
  50 + for (var point in barcode.corners!) {
  51 + final adjustedWith = point.dx ;
  52 + final adjustedHeight= point.dy ;
  53 + points.add(Offset(adjustedWith / size, adjustedHeight / size));
  54 + // points.add(Offset((point.dx ) / size,
  55 + // (point.dy) / size));
  56 + // final differenceWidth = (args.wantedSize!.width - args.size.width) / 2;
  57 + // final differenceHeight = (args.wantedSize!.height - args.size.height) / 2;
  58 + // points.add(Offset((point.dx + differenceWidth) / size,
  59 + // (point.dy + differenceHeight) / size));
  60 + }
  61 + this.points = points;
  62 + });
  63 + }
  64 + }
  65 + // Default 640 x480
  66 + }),
  67 + CustomPaint(
  68 + painter: OpenPainter(points),
  69 + ),
  70 + // Container(
  71 + // alignment: Alignment.bottomCenter,
  72 + // margin: EdgeInsets.only(bottom: 80.0),
  73 + // child: IconButton(
  74 + // icon: ValueListenableBuilder(
  75 + // valueListenable: cameraController.torchState,
  76 + // builder: (context, state, child) {
  77 + // final color =
  78 + // state == TorchState.off ? Colors.grey : Colors.white;
  79 + // return Icon(Icons.bolt, color: color);
  80 + // },
  81 + // ),
  82 + // iconSize: 32.0,
  83 + // onPressed: () => cameraController.torch(),
  84 + // ),
  85 + // ),
  86 + ],
  87 + );
  88 + }),
  89 + ),
  90 + );
  91 + }
  92 +
  93 + @override
  94 + void dispose() {
  95 + // cameraController.dispose();
  96 + super.dispose();
  97 + }
  98 +
  99 + void display(Barcode barcode) {
  100 + Navigator.of(context).popAndPushNamed('display', arguments: barcode);
  101 + }
  102 +}
  103 +
  104 +class OpenPainter extends CustomPainter {
  105 + final List<Offset> points;
  106 +
  107 + OpenPainter(this.points);
  108 + @override
  109 + void paint(Canvas canvas, Size size) {
  110 + var paint1 = Paint()
  111 + ..color = Color(0xff63aa65)
  112 + ..strokeWidth = 10;
  113 + //draw points on canvas
  114 + canvas.drawPoints(PointMode.points, points, paint1);
  115 + }
  116 +
  117 + @override
  118 + bool shouldRepaint(CustomPainter oldDelegate) => true;
  119 +}
  120 +
  121 +class OpacityCurve extends Curve {
  122 + @override
  123 + double transform(double t) {
  124 + if (t < 0.1) {
  125 + return t * 10;
  126 + } else if (t <= 0.9) {
  127 + return 1.0;
  128 + } else {
  129 + return (1.0 - t) * 10;
  130 + }
  131 + }
  132 +}
  133 +
  134 +// import 'package:flutter/material.dart';
  135 +// import 'package:flutter/rendering.dart';
  136 +// import 'package:mobile_scanner/mobile_scanner.dart';
  137 +//
  138 +// void main() {
  139 +// debugPaintSizeEnabled = false;
  140 +// runApp(HomePage());
  141 +// }
  142 +//
  143 +// class HomePage extends StatefulWidget {
  144 +// @override
  145 +// HomeState createState() => HomeState();
  146 +// }
  147 +//
  148 +// class HomeState extends State<HomePage> {
  149 +// @override
  150 +// Widget build(BuildContext context) {
  151 +// return MaterialApp(home: MyApp());
  152 +// }
  153 +// }
  154 +//
  155 +// class MyApp extends StatefulWidget {
  156 +// @override
  157 +// _MyAppState createState() => _MyAppState();
  158 +// }
  159 +//
  160 +// class _MyAppState extends State<MyApp> {
  161 +// String? qr;
  162 +// bool camState = false;
  163 +//
  164 +// @override
  165 +// initState() {
  166 +// super.initState();
  167 +// }
  168 +//
  169 +// @override
  170 +// Widget build(BuildContext context) {
  171 +// return Scaffold(
  172 +// appBar: AppBar(
  173 +// title: Text('Plugin example app'),
  174 +// ),
  175 +// body: Center(
  176 +// child: Column(
  177 +// crossAxisAlignment: CrossAxisAlignment.center,
  178 +// mainAxisAlignment: MainAxisAlignment.center,
  179 +// children: <Widget>[
  180 +// Expanded(
  181 +// child: camState
  182 +// ? Center(
  183 +// child: SizedBox(
  184 +// width: 300.0,
  185 +// height: 600.0,
  186 +// child: MobileScanner(
  187 +// onError: (context, error) => Text(
  188 +// error.toString(),
  189 +// style: TextStyle(color: Colors.red),
  190 +// ),
  191 +// qrCodeCallback: (code) {
  192 +// setState(() {
  193 +// qr = code;
  194 +// });
  195 +// },
  196 +// child: Container(
  197 +// decoration: BoxDecoration(
  198 +// color: Colors.transparent,
  199 +// border: Border.all(
  200 +// color: Colors.orange,
  201 +// width: 10.0,
  202 +// style: BorderStyle.solid),
  203 +// ),
  204 +// ),
  205 +// ),
  206 +// ),
  207 +// )
  208 +// : Center(child: Text("Camera inactive"))),
  209 +// Text("QRCODE: $qr"),
  210 +// ],
  211 +// ),
  212 +// ),
  213 +// floatingActionButton: FloatingActionButton(
  214 +// child: Text(
  215 +// "press me",
  216 +// textAlign: TextAlign.center,
  217 +// ),
  218 +// onPressed: () {
  219 +// setState(() {
  220 +// camState = !camState;
  221 +// });
  222 +// }),
  223 +// );
  224 +// }
  225 +// }
@@ -3,18 +3,22 @@ import 'package:mobile_scanner/mobile_scanner.dart'; @@ -3,18 +3,22 @@ import 'package:mobile_scanner/mobile_scanner.dart';
3 3
4 import 'mobile_scanner_arguments.dart'; 4 import 'mobile_scanner_arguments.dart';
5 5
  6 +enum Ratio {
  7 + ratio_4_3,
  8 + ratio_16_9
  9 +}
  10 +
6 /// A widget showing a live camera preview. 11 /// A widget showing a live camera preview.
7 class MobileScanner extends StatefulWidget { 12 class MobileScanner extends StatefulWidget {
8 /// The controller of the camera. 13 /// The controller of the camera.
9 final MobileScannerController? controller; 14 final MobileScannerController? controller;
10 final Function(Barcode barcode, MobileScannerArguments args)? onDetect; 15 final Function(Barcode barcode, MobileScannerArguments args)? onDetect;
11 - final bool fitScreen;  
12 - final bool fitWidth; 16 + final BoxFit fit;
13 17
14 /// Create a [MobileScanner] with a [controller], the [controller] must has been initialized. 18 /// Create a [MobileScanner] with a [controller], the [controller] must has been initialized.
15 const MobileScanner( 19 const MobileScanner(
16 - {Key? key, this.onDetect, this.controller, this.fitScreen = true, this.fitWidth = true})  
17 - : super(key: key); 20 + {Key? key, this.onDetect, this.controller, this.fit = BoxFit.cover})
  21 + : assert((controller != null )), super(key: key);
18 22
19 @override 23 @override
20 State<MobileScanner> createState() => _MobileScannerState(); 24 State<MobileScanner> createState() => _MobileScannerState();
@@ -23,19 +27,28 @@ class MobileScanner extends StatefulWidget { @@ -23,19 +27,28 @@ class MobileScanner extends StatefulWidget {
23 class _MobileScannerState extends State<MobileScanner> 27 class _MobileScannerState extends State<MobileScanner>
24 with WidgetsBindingObserver { 28 with WidgetsBindingObserver {
25 bool onScreen = true; 29 bool onScreen = true;
26 - MobileScannerController? controller; 30 + late MobileScannerController controller;
  31 +
  32 + @override
  33 + void initState() {
  34 + super.initState();
  35 + if (widget.controller == null) {
  36 + controller = MobileScannerController();
  37 + } else {
  38 + controller = widget.controller!;
  39 + }
  40 + }
27 41
28 @override 42 @override
29 void didChangeAppLifecycleState(AppLifecycleState state) { 43 void didChangeAppLifecycleState(AppLifecycleState state) {
30 if (state == AppLifecycleState.resumed) { 44 if (state == AppLifecycleState.resumed) {
31 setState(() => onScreen = true); 45 setState(() => onScreen = true);
32 } else { 46 } else {
33 - if (controller != null && onScreen) {  
34 - controller!.stop(); 47 + if (onScreen) {
  48 + controller.stop();
35 } 49 }
36 setState(() { 50 setState(() {
37 onScreen = false; 51 onScreen = false;
38 - controller = null;  
39 }); 52 });
40 } 53 }
41 } 54 }
@@ -43,28 +56,28 @@ class _MobileScannerState extends State<MobileScanner> @@ -43,28 +56,28 @@ class _MobileScannerState extends State<MobileScanner>
43 @override 56 @override
44 Widget build(BuildContext context) { 57 Widget build(BuildContext context) {
45 return LayoutBuilder(builder: (context, BoxConstraints constraints) { 58 return LayoutBuilder(builder: (context, BoxConstraints constraints) {
46 - final media = MediaQuery.of(context);  
47 -  
48 - controller ??= MobileScannerController(context,  
49 - width: constraints.maxWidth, height: constraints.maxHeight);  
50 if (!onScreen) return const Text("Camera Paused."); 59 if (!onScreen) return const Text("Camera Paused.");
51 return ValueListenableBuilder( 60 return ValueListenableBuilder(
52 - valueListenable: controller!.args, 61 + valueListenable: controller.args,
53 builder: (context, value, child) { 62 builder: (context, value, child) {
54 value = value as MobileScannerArguments?; 63 value = value as MobileScannerArguments?;
55 if (value == null) { 64 if (value == null) {
56 return Container(color: Colors.black); 65 return Container(color: Colors.black);
57 } else { 66 } else {
58 - controller!.barcodes.listen( 67 + controller.barcodes.listen(
59 (a) => widget.onDetect!(a, value as MobileScannerArguments)); 68 (a) => widget.onDetect!(a, value as MobileScannerArguments));
60 - // Texture(textureId: value.textureId) 69 + debugPrint(' size MediaQuery ${MediaQuery.of(context).size}');
61 return ClipRect( 70 return ClipRect(
62 - child: FittedBox(  
63 - fit: BoxFit.cover,  
64 - child: SizedBox(  
65 - width: value.size.width,  
66 - height: value.size.height,  
67 - child: Texture(textureId: value.textureId), 71 + child: SizedBox(
  72 + width: MediaQuery.of(context).size.width,
  73 + height: MediaQuery.of(context).size.height,
  74 + child: FittedBox(
  75 + fit: widget.fit,
  76 + child: SizedBox(
  77 + width: value.size.width,
  78 + height: value.size.height,
  79 + child: Texture(textureId: value.textureId),
  80 + ),
68 ), 81 ),
69 ), 82 ),
70 ); 83 );
@@ -75,17 +88,7 @@ class _MobileScannerState extends State<MobileScanner> @@ -75,17 +88,7 @@ class _MobileScannerState extends State<MobileScanner>
75 88
76 @override 89 @override
77 void dispose() { 90 void dispose() {
78 - controller?.dispose(); 91 + controller.dispose();
79 super.dispose(); 92 super.dispose();
80 } 93 }
81 -}  
82 -  
83 -extension on Size {  
84 - double fill(Size targetSize) {  
85 - if (targetSize.aspectRatio < aspectRatio) {  
86 - return targetSize.height * aspectRatio / targetSize.width;  
87 - } else {  
88 - return targetSize.width / aspectRatio / targetSize.height;  
89 - }  
90 - }  
91 -} 94 +}
@@ -8,9 +8,8 @@ class MobileScannerArguments { @@ -8,9 +8,8 @@ class MobileScannerArguments {
8 /// Size of the texture. 8 /// Size of the texture.
9 final Size size; 9 final Size size;
10 10
11 - /// Size of the texture.  
12 - final Size? wantedSize; 11 + final bool hasTorch;
13 12
14 /// Create a [MobileScannerArguments]. 13 /// Create a [MobileScannerArguments].
15 - MobileScannerArguments({required this.textureId,required this.size, this.wantedSize}); 14 + MobileScannerArguments({required this.textureId,required this.size, required this.hasTorch});
16 } 15 }
@@ -2,9 +2,9 @@ import 'dart:async'; @@ -2,9 +2,9 @@ import 'dart:async';
2 2
3 import 'package:flutter/cupertino.dart'; 3 import 'package:flutter/cupertino.dart';
4 import 'package:flutter/services.dart'; 4 import 'package:flutter/services.dart';
  5 +import 'package:mobile_scanner/mobile_scanner.dart';
5 6
6 import 'mobile_scanner_arguments.dart'; 7 import 'mobile_scanner_arguments.dart';
7 -import 'objects/barcode.dart';  
8 import 'objects/barcode_utility.dart'; 8 import 'objects/barcode_utility.dart';
9 9
10 /// The facing of a camera. 10 /// The facing of a camera.
@@ -16,11 +16,7 @@ enum CameraFacing { @@ -16,11 +16,7 @@ enum CameraFacing {
16 back, 16 back,
17 } 17 }
18 18
19 -enum MobileScannerState {  
20 - undetermined,  
21 - authorized,  
22 - denied  
23 -} 19 +enum MobileScannerState { undetermined, authorized, denied }
24 20
25 /// The state of torch. 21 /// The state of torch.
26 enum TorchState { 22 enum TorchState {
@@ -31,85 +27,52 @@ enum TorchState { @@ -31,85 +27,52 @@ enum TorchState {
31 on, 27 on,
32 } 28 }
33 29
34 -  
35 -  
36 -// /// A camera controller.  
37 -// abstract class CameraController {  
38 -// /// Arguments for [CameraView].  
39 -// ValueNotifier<CameraArgs?> get args;  
40 -//  
41 -// /// Torch state of the camera.  
42 -// ValueNotifier<TorchState> get torchState;  
43 -//  
44 -// /// A stream of barcodes.  
45 -// Stream<Barcode> get barcodes;  
46 -//  
47 -// /// Create a [CameraController].  
48 -// ///  
49 -// /// [facing] target facing used to select camera.  
50 -// ///  
51 -// /// [formats] the barcode formats for image analyzer.  
52 -// factory CameraController([CameraFacing facing = CameraFacing.back] ) =>  
53 -// _CameraController(facing);  
54 -//  
55 -// /// Start the camera asynchronously.  
56 -// Future<void> start();  
57 -//  
58 -// /// Switch the torch's state.  
59 -// void torch();  
60 -//  
61 -// /// Release the resources of the camera.  
62 -// void dispose();  
63 -// } 30 +enum AnalyzeMode { none, barcode }
64 31
65 class MobileScannerController { 32 class MobileScannerController {
  33 + MethodChannel methodChannel =
  34 + const MethodChannel('dev.steenbakker.mobile_scanner/scanner/method');
  35 + EventChannel eventChannel =
  36 + const EventChannel('dev.steenbakker.mobile_scanner/scanner/event');
66 37
67 - static const MethodChannel method =  
68 - MethodChannel('dev.steenbakker.mobile_scanner/scanner/method');  
69 - static const EventChannel event =  
70 - EventChannel('dev.steenbakker.mobile_scanner/scanner/event');  
71 -  
72 -  
73 -  
74 - static const analyze_none = 0;  
75 - static const analyze_barcode = 1; 38 + int? _controllerHashcode;
  39 + StreamSubscription? events;
76 40
77 - static int? id;  
78 - static StreamSubscription? subscription;  
79 41
80 - final CameraFacing facing;  
81 - final ValueNotifier<MobileScannerArguments?> args;  
82 - final ValueNotifier<TorchState> torchState; 42 + final ValueNotifier<MobileScannerArguments?> args = ValueNotifier(null);
  43 + final ValueNotifier<TorchState> torchState = ValueNotifier(TorchState.off);
  44 + late final ValueNotifier<CameraFacing> cameraFacingState;
  45 + final Ratio? ratio;
  46 + final bool? torchEnabled;
83 47
84 - bool torchable; 48 + CameraFacing facing;
  49 + bool hasTorch = false;
85 late StreamController<Barcode> barcodesController; 50 late StreamController<Barcode> barcodesController;
86 51
87 Stream<Barcode> get barcodes => barcodesController.stream; 52 Stream<Barcode> get barcodes => barcodesController.stream;
88 53
89 - MobileScannerController(BuildContext context, {required num width, required num height, this.facing = CameraFacing.back})  
90 - : args = ValueNotifier(null),  
91 - torchState = ValueNotifier(TorchState.off),  
92 - torchable = false {  
93 - // In case new instance before dispose.  
94 - if (id != null) { 54 + MobileScannerController(
  55 + {this.facing = CameraFacing.back, this.ratio, this.torchEnabled}) {
  56 + // In case a new instance is created before calling dispose()
  57 + if (_controllerHashcode != null) {
95 stop(); 58 stop();
96 } 59 }
97 - id = hashCode;  
98 - // Create barcode stream controller. 60 + _controllerHashcode = hashCode;
  61 +
  62 + cameraFacingState = ValueNotifier(facing);
  63 +
  64 + // Sets analyze mode and barcode stream
99 barcodesController = StreamController.broadcast( 65 barcodesController = StreamController.broadcast(
100 - onListen: () => tryAnalyze(analyze_barcode),  
101 - onCancel: () => tryAnalyze(analyze_none), 66 + onListen: () => setAnalyzeMode(AnalyzeMode.barcode.index),
  67 + onCancel: () => setAnalyzeMode(AnalyzeMode.none.index),
102 ); 68 );
103 69
104 - final devicePixelRatio = MediaQuery.of(context).devicePixelRatio;  
105 - 70 + start();
106 71
107 - start(  
108 - width: (devicePixelRatio * width.toInt()).ceil(),  
109 - height: (devicePixelRatio * height.toInt()).ceil());  
110 - // Listen event handler.  
111 - subscription =  
112 - event.receiveBroadcastStream().listen((data) => handleEvent(data)); 72 + // Listen to events from the platform specific code
  73 + events = eventChannel
  74 + .receiveBroadcastStream()
  75 + .listen((data) => handleEvent(data));
113 } 76 }
114 77
115 void handleEvent(Map<dynamic, dynamic> event) { 78 void handleEvent(Map<dynamic, dynamic> event) {
@@ -129,76 +92,91 @@ class MobileScannerController { @@ -129,76 +92,91 @@ class MobileScannerController {
129 } 92 }
130 } 93 }
131 94
132 - void tryAnalyze(int mode) {  
133 - if (hashCode != id) { 95 + void setAnalyzeMode(int mode) {
  96 + if (hashCode != _controllerHashcode) {
134 return; 97 return;
135 } 98 }
136 - method.invokeMethod('analyze', mode); 99 + methodChannel.invokeMethod('analyze', mode);
137 } 100 }
138 101
139 - Future<void> start({  
140 - int? width,  
141 - int? height,  
142 - // List<BarcodeFormats>? formats = _defaultBarcodeFormats,  
143 - }) async { 102 + // List<BarcodeFormats>? formats = _defaultBarcodeFormats,
  103 + /// Start barcode scanning. This will first check if the required permissions
  104 + /// are set.
  105 + Future<void> start() async {
144 ensure('startAsync'); 106 ensure('startAsync');
145 - // Check authorization state.  
146 - MobileScannerState state = MobileScannerState.values[await method.invokeMethod('state')]; 107 +
  108 + setAnalyzeMode(AnalyzeMode.barcode.index);
  109 + // Check authorization status
  110 + MobileScannerState state = MobileScannerState.values[await methodChannel.invokeMethod('state')];
147 switch (state) { 111 switch (state) {
148 case MobileScannerState.undetermined: 112 case MobileScannerState.undetermined:
149 - final bool result = await method.invokeMethod('request');  
150 - state = result ? MobileScannerState.authorized : MobileScannerState.denied;  
151 - break;  
152 - case MobileScannerState.authorized: 113 + final bool result = await methodChannel.invokeMethod('request');
  114 + state =
  115 + result ? MobileScannerState.authorized : MobileScannerState.denied;
153 break; 116 break;
154 case MobileScannerState.denied: 117 case MobileScannerState.denied:
155 throw PlatformException(code: 'NO ACCESS'); 118 throw PlatformException(code: 'NO ACCESS');
  119 + case MobileScannerState.authorized:
  120 + break;
156 } 121 }
157 122
158 - debugPrint('TARGET RESOLUTION $width, $height');  
159 - // Start camera.  
160 - final answer =  
161 - await method.invokeMapMethod<String, dynamic>('start', {  
162 - 'targetWidth': width,  
163 - 'targetHeight': height,  
164 - 'facing': facing.index  
165 - });  
166 - final textureId = answer?['textureId'];  
167 - final Size size = toSize(answer?['size']);  
168 - debugPrint('RECEIVED SIZE: ${size.width} ${size.height}');  
169 - if (width != null && height != null) {  
170 - args.value = MobileScannerArguments(textureId: textureId, size: size, wantedSize: Size(width.toDouble(), height.toDouble()));  
171 - } else {  
172 - args.value = MobileScannerArguments(textureId: textureId, size: size);  
173 - } 123 + cameraFacingState.value = facing;
  124 +
  125 + // Set the starting arguments for the camera
  126 + Map arguments = {};
  127 + arguments['facing'] = facing.index;
  128 + if (ratio != null) arguments['ratio'] = ratio;
  129 + if (torchEnabled != null) arguments['torch'] = torchEnabled;
  130 +
  131 + // Start the camera with arguments
  132 + final Map<String, dynamic>? startResult = await methodChannel.invokeMapMethod<String, dynamic>(
  133 + 'start', arguments);
174 134
175 - torchable = answer?['torchable']; 135 + if (startResult == null) throw PlatformException(code: 'INITIALIZATION ERROR');
  136 +
  137 + hasTorch = startResult['torchable'];
  138 + args.value = MobileScannerArguments(textureId: startResult['textureId'], size: toSize(startResult['size']), hasTorch: hasTorch);
176 } 139 }
177 140
178 - void torch() {  
179 - ensure('torch');  
180 - if (!torchable) return;  
181 - var state = 141 + Future<void> stop() async => await methodChannel.invokeMethod('stop');
  142 +
  143 + /// Switches the torch on or off.
  144 + ///
  145 + /// Only works if torch is available.
  146 + void toggleTorch() {
  147 + ensure('toggleTorch');
  148 + if (!hasTorch) return;
  149 + TorchState state =
182 torchState.value == TorchState.off ? TorchState.on : TorchState.off; 150 torchState.value == TorchState.off ? TorchState.on : TorchState.off;
183 - method.invokeMethod('torch', state.index); 151 + methodChannel.invokeMethod('torch', state.index);
184 } 152 }
185 153
  154 + /// Switches the torch on or off.
  155 + ///
  156 + /// Only works if torch is available.
  157 + Future<void> switchCamera() async {
  158 + ensure('switchCamera');
  159 + await stop();
  160 + facing = facing == CameraFacing.back ? CameraFacing.front : CameraFacing.back;
  161 + start();
  162 + }
  163 +
  164 + /// Disposes the controller and closes all listeners.
186 void dispose() { 165 void dispose() {
187 - if (hashCode == id) { 166 + if (hashCode == _controllerHashcode) {
188 stop(); 167 stop();
189 - subscription?.cancel();  
190 - subscription = null;  
191 - id = null; 168 + events?.cancel();
  169 + events = null;
  170 + _controllerHashcode = null;
192 } 171 }
193 barcodesController.close(); 172 barcodesController.close();
194 } 173 }
195 174
196 - void stop() => method.invokeMethod('stop');  
197 - 175 + /// Checks if the controller is bound to the correct MobileScanner object.
198 void ensure(String name) { 176 void ensure(String name) {
199 final message = 177 final message =
200 'CameraController.$name called after CameraController.dispose\n' 178 'CameraController.$name called after CameraController.dispose\n'
201 'CameraController methods should not be used after calling dispose.'; 179 'CameraController methods should not be used after calling dispose.';
202 - assert(hashCode == id, message); 180 + assert(hashCode == _controllerHashcode, message);
203 } 181 }
204 } 182 }
1 name: mobile_scanner 1 name: mobile_scanner
2 -description: An universal scanner for Flutter based on MLKit. 2 +description: An universal scanner for Flutter based on MLKit. Uses CameraX on Android and AVFoundation on iOS.
3 version: 0.0.1 3 version: 0.0.1
4 -homepage: 4 +repository: https://github.com/juliansteenbakker/mobile_scanner
5 5
6 environment: 6 environment:
7 sdk: ">=2.16.0 <3.0.0" 7 sdk: ">=2.16.0 <3.0.0"