Julian Steenbakker

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

package dev.steenbakker.mobile_scanner
import androidx.annotation.IntDef
@IntDef(AnalyzeMode.NONE, AnalyzeMode.BARCODE)
@Target(AnnotationTarget.FIELD)
@Retention(AnnotationRetention.SOURCE)
annotation class AnalyzeMode {
companion object {
const val NONE = 0
const val BARCODE = 1
}
}
\ No newline at end of file
... ...
package dev.steenbakker.mobile_scanner.old
package dev.steenbakker.mobile_scanner
import com.google.mlkit.vision.barcode.BarcodeScannerOptions
import java.util.ArrayList
... ... @@ -81,12 +81,12 @@ enum class BarcodeFormats(val intValue: Int) {
init {
val values = values()
// formatsMap =
// HashMap<String, Int>(com.github.rmtmckenzie.qrmobilevision.values.size * 4 / 3)
// for (value in com.github.rmtmckenzie.qrmobilevision.values) {
// formatsMap!![com.github.rmtmckenzie.qrmobilevision.value.name] =
// com.github.rmtmckenzie.qrmobilevision.value.intValue
// }
formatsMap =
HashMap<String, Int>(values.size * 4 / 3)
for (value in values) {
formatsMap!![value.name] =
value.intValue
}
}
}
}
\ No newline at end of file
... ...
package dev.steenbakker.mobile_scanner
import android.Manifest
import android.R.attr.height
import android.R.attr.width
import android.annotation.SuppressLint
import android.app.Activity
import android.content.Context
import android.content.pm.PackageManager
import android.graphics.ImageFormat
import android.graphics.SurfaceTexture
import android.hardware.camera2.CameraCharacteristics
import android.hardware.camera2.CameraManager
import android.hardware.camera2.params.StreamConfigurationMap
import android.graphics.Point
import android.util.Log
import android.util.Rational
import android.util.Size
import android.view.Surface
import android.view.Surface.ROTATION_0
import android.view.Surface.ROTATION_180
import androidx.annotation.IntDef
import androidx.annotation.NonNull
import androidx.camera.core.*
import androidx.camera.core.impl.PreviewConfig
import androidx.camera.lifecycle.ProcessCameraProvider
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import androidx.lifecycle.LifecycleOwner
import com.google.mlkit.vision.barcode.BarcodeScannerOptions
import com.google.mlkit.vision.barcode.BarcodeScanning
import com.google.mlkit.vision.barcode.common.Barcode
import com.google.mlkit.vision.common.InputImage
import io.flutter.plugin.common.EventChannel
import io.flutter.plugin.common.MethodCall
... ... @@ -38,7 +27,8 @@ import io.flutter.view.TextureRegistry
class MobileScanner(private val activity: Activity, private val textureRegistry: TextureRegistry)
: MethodChannel.MethodCallHandler, EventChannel.StreamHandler, PluginRegistry.RequestPermissionsResultListener {
companion object {
private const val REQUEST_CODE = 19930430
private const val REQUEST_CODE = 22022022
private val TAG = MobileScanner::class.java.simpleName
}
private var sink: EventChannel.EventSink? = null
... ... @@ -54,12 +44,12 @@ class MobileScanner(private val activity: Activity, private val textureRegistry:
@ExperimentalGetImage
override fun onMethodCall(@NonNull call: MethodCall, @NonNull result: MethodChannel.Result) {
when (call.method) {
"state" -> stateNative(result)
"request" -> requestNative(result)
"start" -> startNative(call, result)
"torch" -> torchNative(call, result)
"analyze" -> analyzeNative(call, result)
"stop" -> stopNative(result)
"state" -> checkPermission(result)
"request" -> requestPermission(result)
"start" -> start(call, result)
"torch" -> switchTorch(call, result)
"analyze" -> switchAnalyzeMode(call, result)
"stop" -> stop(result)
else -> result.notImplemented()
}
}
... ... @@ -76,7 +66,7 @@ class MobileScanner(private val activity: Activity, private val textureRegistry:
return listener?.onRequestPermissionsResult(requestCode, permissions, grantResults) ?: false
}
private fun stateNative(result: MethodChannel.Result) {
private fun checkPermission(result: MethodChannel.Result) {
// Can't get exact denied or not_determined state without request. Just return not_determined when state isn't authorized
val state =
if (ContextCompat.checkSelfPermission(activity, Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED) 1
... ... @@ -84,7 +74,7 @@ class MobileScanner(private val activity: Activity, private val textureRegistry:
result.success(state)
}
private fun requestNative(result: MethodChannel.Result) {
private fun requestPermission(result: MethodChannel.Result) {
listener = PluginRegistry.RequestPermissionsResultListener { requestCode, _, grantResults ->
if (requestCode != REQUEST_CODE) {
false
... ... @@ -99,113 +89,115 @@ class MobileScanner(private val activity: Activity, private val textureRegistry:
ActivityCompat.requestPermissions(activity, permissions, REQUEST_CODE)
}
private var sensorOrientation = 0
@ExperimentalGetImage
// @androidx.camera.camera2.interop.ExperimentalCamera2Interop
private fun startNative(call: MethodCall, result: MethodChannel.Result) {
val analyzer = ImageAnalysis.Analyzer { imageProxy -> // YUV_420_888 format
when (analyzeMode) {
AnalyzeMode.BARCODE -> {
val mediaImage = imageProxy.image ?: return@Analyzer
val inputImage = InputImage.fromMediaImage(mediaImage, imageProxy.imageInfo.rotationDegrees)
val targetWidth: Int? = call.argument<Int>("targetWidth")
val targetHeight: Int? = call.argument<Int>("targetHeight")
val facing: Int? = call.argument<Int>("facing")
if (targetWidth == null || targetHeight == null) {
result.error("INVALID_ARGUMENT", "Missing a required argument", "Expecting targetWidth, targetHeight")
return
scanner.process(inputImage)
.addOnSuccessListener { barcodes ->
for (barcode in barcodes) {
val event = mapOf("name" to "barcode", "data" to barcode.data)
sink?.success(event)
}
}
.addOnFailureListener { e -> Log.e(TAG, e.message, e) }
.addOnCompleteListener { imageProxy.close() }
}
else -> imageProxy.close()
}
}
private var scanner = BarcodeScanning.getClient()
Log.i("LOG", "Target resolution : $targetWidth, $targetHeight")
@ExperimentalGetImage
private fun start(call: MethodCall, result: MethodChannel.Result) {
val facing: Int = call.argument<Int>("facing") ?: 0
val ratio: Int? = call.argument<Int>("ratio")
val torch: Boolean = call.argument<Boolean>("torch") ?: false
val formatStrings: List<String>? = call.argument<List<String>>("formats")
if (formatStrings != null) {
val options: BarcodeScannerOptions = BarcodeFormats.optionsFromStringList(formatStrings)
scanner = BarcodeScanning.getClient(options)
}
val future = ProcessCameraProvider.getInstance(activity)
val executor = ContextCompat.getMainExecutor(activity)
future.addListener({
cameraProvider = future.get()
textureEntry = textureRegistry.createSurfaceTexture()
val textureId = textureEntry!!.id()
// Preview
val surfaceProvider = Preview.SurfaceProvider { request ->
val texture = textureEntry!!.surfaceTexture()
val resolution = request.resolution
texture.setDefaultBufferSize(resolution.width, resolution.height)
Log.i("LOG", "Image resolution : ${request.resolution}")
texture.setDefaultBufferSize(request.resolution.width, request.resolution.height)
val surface = Surface(texture)
request.provideSurface(surface, executor) { }
}
// PreviewConfig().apply { }
// val previewConfig = PreviewConfig.Builder().apply {
// setTargetAspectRatio(SQUARE_ASPECT_RATIO)
// setTargetRotation(viewFinder.display.rotation)
// }.build()
val preview = Preview.Builder()
.setTargetResolution(Size(targetWidth, targetHeight))
.build().apply { setSurfaceProvider(surfaceProvider) }
// Analyzer
val analyzer = ImageAnalysis.Analyzer { imageProxy -> // YUV_420_888 format
when (analyzeMode) {
AnalyzeMode.BARCODE -> {
val mediaImage = imageProxy.image ?: return@Analyzer
val inputImage = InputImage.fromMediaImage(mediaImage, imageProxy.imageInfo.rotationDegrees)
val scanner = BarcodeScanning.getClient()
scanner.process(inputImage)
.addOnSuccessListener { barcodes ->
for (barcode in barcodes) {
val event = mapOf("name" to "barcode", "data" to barcode.data)
sink?.success(event)
}
}
.addOnFailureListener { e -> Log.e(TAG, e.message, e) }
.addOnCompleteListener { imageProxy.close() }
}
else -> imageProxy.close()
}
// Build the preview to be shown on the Flutter texture
val previewBuilder = Preview.Builder()
if (ratio != null) {
previewBuilder.setTargetAspectRatio(ratio)
}
val analysis = ImageAnalysis.Builder()
val preview = previewBuilder.build().apply { setSurfaceProvider(surfaceProvider) }
// Build the analyzer to be passed on to MLKit
val analysisBuilder = ImageAnalysis.Builder()
.setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
.setTargetResolution(Size(targetWidth, targetHeight))
.build().apply { setAnalyzer(executor, analyzer) }
// Bind to lifecycle.
val owner = activity as LifecycleOwner
val selector =
if (call.arguments == 0) CameraSelector.DEFAULT_FRONT_CAMERA
else CameraSelector.DEFAULT_BACK_CAMERA
camera = cameraProvider!!.bindToLifecycle(owner, selector, preview, analysis)
val analysisSize = analysis.resolutionInfo?.resolution ?: Size(0, 0)
val previewSize = preview.resolutionInfo?.resolution ?: Size(0, 0)
Log.i("LOG", "Analyzer: $analysisSize")
Log.i("LOG", "Preview: $previewSize")
camera!!.cameraInfo.torchState.observe(owner) { state ->
if (ratio != null) {
analysisBuilder.setTargetAspectRatio(ratio)
}
val analysis = analysisBuilder.build().apply { setAnalyzer(executor, analyzer) }
// Select the correct camera
val selector = if (facing == 0) CameraSelector.DEFAULT_FRONT_CAMERA else CameraSelector.DEFAULT_BACK_CAMERA
camera = cameraProvider!!.bindToLifecycle(activity as LifecycleOwner, selector, preview, analysis)
val analysisSize = analysis.resolutionInfo?.resolution ?: Size(0, 0)
val previewSize = preview.resolutionInfo?.resolution ?: Size(0, 0)
Log.i("LOG", "Analyzer: $analysisSize")
Log.i("LOG", "Preview: $previewSize")
// Register the torch listener
camera!!.cameraInfo.torchState.observe(activity) { state ->
// TorchState.OFF = 0; TorchState.ON = 1
val event = mapOf("name" to "torchState", "data" to state)
sink?.success(event)
sink?.success(mapOf("name" to "torchState", "data" to state))
}
// Enable torch if provided
camera!!.cameraControl.enableTorch(torch)
val resolution = preview.resolutionInfo!!.resolution
val portrait = camera!!.cameraInfo.sensorRotationDegrees % 180 == 0
val width = resolution.width.toDouble()
val height = resolution.height.toDouble()
// val size = mapOf("width" to 1920.0, "height" to 1080.0)
val size = if (portrait) mapOf("width" to width, "height" to height) else mapOf("width" to height, "height" to width)
val answer = mapOf("textureId" to textureId, "size" to size, "torchable" to camera!!.torchable)
val answer = mapOf("textureId" to textureEntry!!.id(), "size" to size, "torchable" to camera!!.cameraInfo.hasFlashUnit())
result.success(answer)
}, executor)
}
private fun torchNative(call: MethodCall, result: MethodChannel.Result) {
private fun switchTorch(call: MethodCall, result: MethodChannel.Result) {
val state = call.arguments == 1
camera!!.cameraControl.enableTorch(state)
result.success(null)
}
private fun analyzeNative(call: MethodCall, result: MethodChannel.Result) {
private fun switchAnalyzeMode(call: MethodCall, result: MethodChannel.Result) {
analyzeMode = call.arguments as Int
result.success(null)
}
private fun stopNative(result: MethodChannel.Result) {
private fun stop(result: MethodChannel.Result) {
val owner = activity as LifecycleOwner
camera!!.cameraInfo.torchState.removeObservers(owner)
cameraProvider!!.unbindAll()
... ... @@ -218,14 +210,60 @@ class MobileScanner(private val activity: Activity, private val textureRegistry:
result.success(null)
}
}
@IntDef(AnalyzeMode.NONE, AnalyzeMode.BARCODE)
@Target(AnnotationTarget.FIELD)
@Retention(AnnotationRetention.SOURCE)
annotation class AnalyzeMode {
companion object {
const val NONE = 0
const val BARCODE = 1
}
private val Barcode.data: Map<String, Any?>
get() = mapOf("corners" to cornerPoints?.map { corner -> corner.data }, "format" to format,
"rawBytes" to rawBytes, "rawValue" to rawValue, "type" to valueType,
"calendarEvent" to calendarEvent?.data, "contactInfo" to contactInfo?.data,
"driverLicense" to driverLicense?.data, "email" to email?.data,
"geoPoint" to geoPoint?.data, "phone" to phone?.data, "sms" to sms?.data,
"url" to url?.data, "wifi" to wifi?.data)
private val Point.data: Map<String, Double>
get() = mapOf("x" to x.toDouble(), "y" to y.toDouble())
private val Barcode.CalendarEvent.data: Map<String, Any?>
get() = mapOf("description" to description, "end" to end?.rawValue, "location" to location,
"organizer" to organizer, "start" to start?.rawValue, "status" to status,
"summary" to summary)
private val Barcode.ContactInfo.data: Map<String, Any?>
get() = mapOf("addresses" to addresses.map { address -> address.data },
"emails" to emails.map { email -> email.data }, "name" to name?.data,
"organization" to organization, "phones" to phones.map { phone -> phone.data },
"title" to title, "urls" to urls)
private val Barcode.Address.data: Map<String, Any?>
get() = mapOf("addressLines" to addressLines, "type" to type)
private val Barcode.PersonName.data: Map<String, Any?>
get() = mapOf("first" to first, "formattedName" to formattedName, "last" to last,
"middle" to middle, "prefix" to prefix, "pronunciation" to pronunciation,
"suffix" to suffix)
private val Barcode.DriverLicense.data: Map<String, Any?>
get() = mapOf("addressCity" to addressCity, "addressState" to addressState,
"addressStreet" to addressStreet, "addressZip" to addressZip, "birthDate" to birthDate,
"documentType" to documentType, "expiryDate" to expiryDate, "firstName" to firstName,
"gender" to gender, "issueDate" to issueDate, "issuingCountry" to issuingCountry,
"lastName" to lastName, "licenseNumber" to licenseNumber, "middleName" to middleName)
private val Barcode.Email.data: Map<String, Any?>
get() = mapOf("address" to address, "body" to body, "subject" to subject, "type" to type)
private val Barcode.GeoPoint.data: Map<String, Any?>
get() = mapOf("latitude" to lat, "longitude" to lng)
private val Barcode.Phone.data: Map<String, Any?>
get() = mapOf("number" to number, "type" to type)
private val Barcode.Sms.data: Map<String, Any?>
get() = mapOf("message" to message, "phoneNumber" to phoneNumber)
private val Barcode.UrlBookmark.data: Map<String, Any?>
get() = mapOf("title" to title, "url" to url)
private val Barcode.WiFi.data: Map<String, Any?>
get() = mapOf("encryptionType" to encryptionType, "password" to password, "ssid" to ssid)
}
\ No newline at end of file
... ...
... ... @@ -7,7 +7,7 @@ import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding
import io.flutter.plugin.common.EventChannel
import io.flutter.plugin.common.MethodChannel
/** CameraXPlugin */
/** MobileScannerPlugin */
class MobileScannerPlugin : FlutterPlugin, ActivityAware {
private var flutter: FlutterPlugin.FlutterPluginBinding? = null
private var activity: ActivityPluginBinding? = null
... ...
package dev.steenbakker.mobile_scanner
import android.graphics.Point
import androidx.camera.core.Camera
import androidx.camera.core.ImageProxy
import com.google.mlkit.vision.barcode.common.Barcode
val Any.TAG: String
get() = javaClass.simpleName
val Camera.torchable: Boolean
get() = cameraInfo.hasFlashUnit()
//
//val ImageProxy.yuv: ByteArray
// get() {
// val ySize = y.buffer.remaining()
// val uSize = u.buffer.remaining()
// val vSize = v.buffer.remaining()
//
// val size = ySize + uSize + vSize
// val data = ByteArray(size)
//
// var offset = 0
// y.buffer.get(data, offset, ySize)
// offset += ySize
// u.buffer.get(data, offset, uSize)
// offset += uSize
// v.buffer.get(data, offset, vSize)
//
// return data
// }
//
//val ImageProxy.nv21: ByteArray
// get() {
// if (BuildConfig.DEBUG) {
// if (y.pixelStride != 1 || u.rowStride != v.rowStride || u.pixelStride != v.pixelStride) {
// error("Assertion failed")
// }
// }
//
// val ySize = width * height
// val uvSize = ySize / 2
// val size = ySize + uvSize
// val data = ByteArray(size)
//
// var offset = 0
// // Y Plane
// if (y.rowStride == width) {
// y.buffer.get(data, offset, ySize)
// offset += ySize
// } else {
// for (row in 0 until height) {
// y.buffer.get(data, offset, width)
// offset += width
// }
//
// if (BuildConfig.DEBUG && offset != ySize) {
// error("Assertion failed")
// }
// }
// // U,V Planes
// if (v.rowStride == width && v.pixelStride == 2) {
// if (BuildConfig.DEBUG && v.size != uvSize - 1) {
// error("Assertion failed")
// }
//
// v.buffer.get(data, offset, 1)
// offset += 1
// u.buffer.get(data, offset, u.size)
// if (BuildConfig.DEBUG) {
// val value = v.buffer.get()
// if (data[offset] != value) {
// error("Assertion failed")
// }
// }
// } else {
// for (row in 0 until height / 2)
// for (col in 0 until width / 2) {
// val index = row * v.rowStride + col * v.pixelStride
// data[offset++] = v.buffer.get(index)
// data[offset++] = u.buffer.get(index)
// }
//
// if (BuildConfig.DEBUG && offset != size) {
// error("Assertion failed")
// }
// }
//
// return data
// }
val ImageProxy.PlaneProxy.size
get() = buffer.remaining()
val ImageProxy.y: ImageProxy.PlaneProxy
get() = planes[0]
val ImageProxy.u: ImageProxy.PlaneProxy
get() = planes[1]
val ImageProxy.v: ImageProxy.PlaneProxy
get() = planes[2]
val Barcode.data: Map<String, Any?>
get() = mapOf("corners" to cornerPoints?.map { corner -> corner.data }, "format" to format,
"rawBytes" to rawBytes, "rawValue" to rawValue, "type" to valueType,
"calendarEvent" to calendarEvent?.data, "contactInfo" to contactInfo?.data,
"driverLicense" to driverLicense?.data, "email" to email?.data,
"geoPoint" to geoPoint?.data, "phone" to phone?.data, "sms" to sms?.data,
"url" to url?.data, "wifi" to wifi?.data)
val Point.data: Map<String, Double>
get() = mapOf("x" to x.toDouble(), "y" to y.toDouble())
val Barcode.CalendarEvent.data: Map<String, Any?>
get() = mapOf("description" to description, "end" to end?.rawValue, "location" to location,
"organizer" to organizer, "start" to start?.rawValue, "status" to status,
"summary" to summary)
val Barcode.ContactInfo.data: Map<String, Any?>
get() = mapOf("addresses" to addresses.map { address -> address.data },
"emails" to emails.map { email -> email.data }, "name" to name?.data,
"organization" to organization, "phones" to phones.map { phone -> phone.data },
"title" to title, "urls" to urls)
val Barcode.Address.data: Map<String, Any?>
get() = mapOf("addressLines" to addressLines, "type" to type)
val Barcode.PersonName.data: Map<String, Any?>
get() = mapOf("first" to first, "formattedName" to formattedName, "last" to last,
"middle" to middle, "prefix" to prefix, "pronunciation" to pronunciation,
"suffix" to suffix)
val Barcode.DriverLicense.data: Map<String, Any?>
get() = mapOf("addressCity" to addressCity, "addressState" to addressState,
"addressStreet" to addressStreet, "addressZip" to addressZip, "birthDate" to birthDate,
"documentType" to documentType, "expiryDate" to expiryDate, "firstName" to firstName,
"gender" to gender, "issueDate" to issueDate, "issuingCountry" to issuingCountry,
"lastName" to lastName, "licenseNumber" to licenseNumber, "middleName" to middleName)
val Barcode.Email.data: Map<String, Any?>
get() = mapOf("address" to address, "body" to body, "subject" to subject, "type" to type)
val Barcode.GeoPoint.data: Map<String, Any?>
get() = mapOf("latitude" to lat, "longitude" to lng)
val Barcode.Phone.data: Map<String, Any?>
get() = mapOf("number" to number, "type" to type)
val Barcode.Sms.data: Map<String, Any?>
get() = mapOf("message" to message, "phoneNumber" to phoneNumber)
val Barcode.UrlBookmark.data: Map<String, Any?>
get() = mapOf("title" to title, "url" to url)
val Barcode.WiFi.data: Map<String, Any?>
get() = mapOf("encryptionType" to encryptionType, "password" to password, "ssid" to ssid)
\ No newline at end of file
package dev.steenbakker.mobile_scanner.exceptions
internal class NoPermissionException : RuntimeException()
\ No newline at end of file
internal class NoPermissionException : RuntimeException()
//internal class Exception(val reason: Reason) :
// java.lang.Exception("Mobile Scanner failed because $reason") {
//
// internal enum class Reason {
// noHardware, noPermissions, noBackCamera
// }
//}
\ No newline at end of file
... ...
package dev.steenbakker.mobile_scanner.old
import android.Manifest
import android.annotation.SuppressLint
import android.app.Activity
import android.content.Context
import android.content.pm.PackageManager
import android.util.Log
import android.view.Surface
import androidx.camera.core.*
import androidx.camera.lifecycle.ProcessCameraProvider
import androidx.core.content.ContextCompat
import androidx.lifecycle.LifecycleOwner
import com.google.mlkit.vision.barcode.BarcodeScannerOptions
import com.google.mlkit.vision.barcode.BarcodeScanning
import com.google.mlkit.vision.common.InputImage
import dev.steenbakker.mobile_scanner.exceptions.NoPermissionException
import io.flutter.plugin.common.MethodChannel
import io.flutter.view.TextureRegistry
import java.io.IOException
internal class MobileScanner(
private val context: Activity,
private val texture: TextureRegistry
) {
private var cameraProvider: ProcessCameraProvider? = null
private var camera: Camera? = null
private var textureEntry: TextureRegistry.SurfaceTextureEntry? = null
@ExperimentalGetImage
@Throws(IOException::class, NoPermissionException::class, Exception::class)
fun start(
result: MethodChannel.Result,
options: BarcodeScannerOptions?,
channel: MethodChannel
) {
if (!hasCameraHardware(context)) {
throw Exception(Exception.Reason.noHardware)
}
if (!checkCameraPermission(context)) {
throw NoPermissionException()
}
textureEntry = texture.createSurfaceTexture()
val textureId = textureEntry!!.id()
val future = ProcessCameraProvider.getInstance(context)
val executor = ContextCompat.getMainExecutor(context)
future.addListener({
// Preview
val surfaceProvider = Preview.SurfaceProvider { request ->
val resolution = request.resolution
val texture = textureEntry!!.surfaceTexture()
texture.setDefaultBufferSize(resolution.width, resolution.height)
val surface = Surface(texture)
request.provideSurface(surface, executor, { })
}
val preview = Preview.Builder().build().apply { setSurfaceProvider(surfaceProvider) }
val analyzer = ImageAnalysis.Analyzer { imageProxy -> // YUV_420_888 format
val mediaImage = imageProxy.image ?: return@Analyzer
val inputImage =
InputImage.fromMediaImage(mediaImage, imageProxy.imageInfo.rotationDegrees)
val scanner = if (options != null) {
BarcodeScanning.getClient(options)
} else {
BarcodeScanning.getClient()
}
scanner.process(inputImage)
.addOnSuccessListener { barcodes ->
val barcodeList: MutableList<Map<String, Any?>> = mutableListOf()
for (barcode in barcodes) {
barcodeList.add(
mapOf(
"value" to barcode.rawValue,
"bytes" to barcode.rawBytes
)
)
}
channel.invokeMethod("qrRead", barcodeList)
}
.addOnFailureListener { e -> Log.e("Camera", e.message, e) }
.addOnCompleteListener { imageProxy.close() }
}
val analysis = ImageAnalysis.Builder()
.setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
.build().apply { setAnalyzer(executor, analyzer) }
// Bind to lifecycle.
val owner = context as LifecycleOwner
// val selector =
// if (call.arguments == 0) CameraSelector.DEFAULT_FRONT_CAMERA
// else CameraSelector.DEFAULT_BACK_CAMERA
camera = cameraProvider!!.bindToLifecycle(
owner,
CameraSelector.DEFAULT_BACK_CAMERA,
preview,
analysis
)
camera!!.cameraInfo.torchState.observe(owner, { state ->
// TorchState.OFF = 0; TorchState.ON = 1
// val event = mapOf("name" to "torchState", "data" to state)
// sink?.success(event)
})
// TODO: seems there's not a better way to get the final resolution
@SuppressLint("RestrictedApi")
val resolution = preview.attachedSurfaceResolution!!
val portrait = camera!!.cameraInfo.sensorRotationDegrees % 180 == 0
val width = resolution.width.toDouble()
val height = resolution.height.toDouble()
val size = if (portrait) mapOf(
"width" to width,
"height" to height
) else mapOf("width" to height, "height" to width)
result.success(mapOf("textureId" to textureId, "size" to size))
}, executor)
}
fun stop() {
val owner = context as LifecycleOwner
camera!!.cameraInfo.torchState.removeObservers(owner)
cameraProvider!!.unbindAll()
textureEntry!!.release()
camera = null
textureEntry = null
cameraProvider = null
}
private fun hasCameraHardware(context: Context): Boolean {
return context.packageManager.hasSystemFeature(PackageManager.FEATURE_CAMERA_ANY)
}
private fun checkCameraPermission(context: Context): Boolean {
val permissions = arrayOf(Manifest.permission.CAMERA)
val res = context.checkCallingOrSelfPermission(permissions[0])
return res == PackageManager.PERMISSION_GRANTED
}
internal class Exception(val reason: Reason) :
java.lang.Exception("Mobile Scanner failed because $reason") {
internal enum class Reason {
noHardware, noPermissions, noBackCamera
}
}
}
\ No newline at end of file
package dev.steenbakker.mobile_scanner.old
import android.Manifest
import android.app.Activity
import android.content.pm.PackageManager
import android.util.Log
import androidx.annotation.NonNull
import androidx.camera.core.ExperimentalGetImage
import androidx.core.app.ActivityCompat
import dev.steenbakker.mobile_scanner.exceptions.NoPermissionException
import io.flutter.embedding.engine.plugins.FlutterPlugin
import io.flutter.embedding.engine.plugins.activity.ActivityAware
import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding
import io.flutter.plugin.common.EventChannel
import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel
import io.flutter.plugin.common.MethodChannel.MethodCallHandler
import io.flutter.plugin.common.MethodChannel.Result
import io.flutter.plugin.common.PluginRegistry.RequestPermissionsResultListener
import io.flutter.view.TextureRegistry
import java.io.IOException
/** MobileScannerPlugin */
class MobileScannerPlugin: FlutterPlugin, MethodCallHandler, ActivityAware,
RequestPermissionsResultListener, EventChannel.StreamHandler {
private lateinit var textures: TextureRegistry
private lateinit var channel : MethodChannel
private lateinit var event : EventChannel
private var activity: Activity? = null
private var waitingForPermissionResult = false
private var sink: EventChannel.EventSink? = null
private var mobileScanner: MobileScanner? = null
override fun onAttachedToActivity(binding: ActivityPluginBinding) {
activity = binding.activity
}
override fun onDetachedFromActivityForConfigChanges() {
onDetachedFromActivity()
}
override fun onReattachedToActivityForConfigChanges(binding: ActivityPluginBinding) {
onAttachedToActivity(binding)
}
override fun onDetachedFromActivity() {
activity = null
}
override fun onDetachedFromEngine(@NonNull binding: FlutterPlugin.FlutterPluginBinding) {
channel.setMethodCallHandler(null)
}
override fun onAttachedToEngine(@NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) {
textures = flutterPluginBinding.textureRegistry
channel = MethodChannel(flutterPluginBinding.binaryMessenger, "dev.steenbakker.mobile_scanner/scanner/method")
event = EventChannel(flutterPluginBinding.binaryMessenger, "dev.steenbakker.mobile_scanner/scanner/event")
channel.setMethodCallHandler(this)
event.setStreamHandler(this)
}
override fun onListen(arguments: Any?, events: EventChannel.EventSink?) {
this.sink = events
}
override fun onCancel(arguments: Any?) {
sink = null
}
override fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array<String?>?,
grantResults: IntArray
): Boolean {
if (requestCode == 105505) {
waitingForPermissionResult = false
if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
Log.i(
"mobile_scanner",
"Permissions request granted."
)
mobileScanner?.stop()
} else {
Log.i(
"mobile_scanner",
"Permissions request denied."
)
mobileScanner?.stop()
}
return true
}
return false
}
@ExperimentalGetImage
override fun onMethodCall(@NonNull call: MethodCall, @NonNull result: Result) {
when (call.method) {
"start" -> {
// val targetWidth: Int? = call.argument<Int>("targetWidth")
// val targetHeight: Int? = call.argument<Int>("targetHeight")
// val formatStrings: List<String>? = call.argument<List<String>>("formats")
// if (targetWidth == null || targetHeight == null) {
// result.error(
// "INVALID_ARGUMENT",
// "Missing a required argument",
// "Expecting targetWidth, targetHeight"
// )
// return
// }
// val options: BarcodeScannerOptions = BarcodeFormats.optionsFromStringList(formatStrings)
// mobileScanner ?:
try {
MobileScanner(activity!!, textures).start(result, null, channel)
} catch (e: IOException) {
e.printStackTrace()
result.error(
"IOException",
"Error starting camera because of IOException: " + e.localizedMessage,
null
)
} catch (e: MobileScanner.Exception) {
e.printStackTrace()
result.error(
e.reason.name,
"Error starting camera for reason: " + e.reason.name,
null
)
} catch (e: NoPermissionException) {
waitingForPermissionResult = true
ActivityCompat.requestPermissions(
activity!!,
arrayOf(Manifest.permission.CAMERA),
105505
)
}
}
"stop" -> {
if (mobileScanner != null && !waitingForPermissionResult) {
mobileScanner!!.stop()
}
result.success(null)
}
else -> result.notImplemented()
}
}
}
... ... @@ -16,57 +16,95 @@ class AnalyzeView extends StatefulWidget {
class _AnalyzeViewState extends State<AnalyzeView>
with SingleTickerProviderStateMixin {
List<Offset> points = [];
String? barcode;
// CameraController cameraController = CameraController(context, width: 320, height: 150);
String? barcode = null;
MobileScannerController controller = MobileScannerController(torchEnabled: true,
facing: CameraFacing.front,);
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
backgroundColor: Colors.black,
body: Builder(builder: (context) {
return Stack(
children: [
MobileScanner(
// fitScreen: false,
// controller: cameraController,
controller: controller,
fit: BoxFit.contain,
// controller: MobileScannerController(
// torchEnabled: true,
// facing: CameraFacing.front,
// ),
onDetect: (barcode, args) {
if (this.barcode != barcode.rawValue) {
this.barcode = barcode.rawValue;
if (barcode.corners != null) {
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
content: Text('${barcode.rawValue}'),
duration: const Duration(milliseconds: 200),
animation: null,
));
setState(() {
final List<Offset> points = [];
double factorWidth = args.size.width / 520;
// double factorHeight = wanted / args.size.height;
final size = MediaQuery.of(context).devicePixelRatio;
debugPrint('Size: ${barcode.corners}');
for (var point in barcode.corners!) {
final adjustedWith = point.dx ;
final adjustedHeight= point.dy ;
points.add(Offset(adjustedWith / size, adjustedHeight / size));
// points.add(Offset((point.dx ) / size,
// (point.dy) / size));
// final differenceWidth = (args.wantedSize!.width - args.size.width) / 2;
// final differenceHeight = (args.wantedSize!.height - args.size.height) / 2;
// points.add(Offset((point.dx + differenceWidth) / size,
// (point.dy + differenceHeight) / size));
}
this.points = points;
});
}
setState(() {
this.barcode = barcode.rawValue;
});
}
// Default 640 x480
}),
CustomPaint(
painter: OpenPainter(points),
Align(
alignment: Alignment.bottomCenter,
child: Container(
alignment: Alignment.bottomCenter,
height: 100,
color: Colors.black.withOpacity(0.4),
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
IconButton(
color: Colors.white,
icon: ValueListenableBuilder(
valueListenable: controller.torchState,
builder: (context, state, child) {
switch (state as TorchState) {
case TorchState.off:
return const Icon(Icons.flash_off, color: Colors.grey);
case TorchState.on:
return const Icon(Icons.flash_on, color: Colors.yellow);
}
},
),
iconSize: 32.0,
onPressed: () => controller.toggleTorch(),
),
Center(
child: SizedBox(
width: MediaQuery.of(context).size.width - 120,
height: 50,
child: FittedBox(
child: Text(
barcode ?? 'Scan something!',
overflow: TextOverflow.fade,
style: Theme.of(context)
.textTheme
.headline4!
.copyWith(color: Colors.white),
),
),
),
),
IconButton(
color: Colors.white,
icon: ValueListenableBuilder(
valueListenable: controller.cameraFacingState,
builder: (context, state, child) {
if (state == CameraFacing.front) {
return const Icon(Icons.camera_front);
} else {
return const Icon(Icons.camera_rear);
}
},
),
iconSize: 32.0,
onPressed: () => controller.switchCamera(),
),
],
),
),
),
// Container(
// alignment: Alignment.bottomCenter,
// margin: EdgeInsets.only(bottom: 80.0),
... ... @@ -101,36 +139,6 @@ class _AnalyzeViewState extends State<AnalyzeView>
}
}
class OpenPainter extends CustomPainter {
final List<Offset> points;
OpenPainter(this.points);
@override
void paint(Canvas canvas, Size size) {
var paint1 = Paint()
..color = Color(0xff63aa65)
..strokeWidth = 10;
//draw points on canvas
canvas.drawPoints(PointMode.points, points, paint1);
}
@override
bool shouldRepaint(CustomPainter oldDelegate) => true;
}
class OpacityCurve extends Curve {
@override
double transform(double t) {
if (t < 0.1) {
return t * 10;
} else if (t <= 0.9) {
return 1.0;
} else {
return (1.0 - t) * 10;
}
}
}
// import 'package:flutter/material.dart';
// import 'package:flutter/rendering.dart';
// import 'package:mobile_scanner/mobile_scanner.dart';
... ...
import 'dart:ui';
import 'package:flutter/material.dart';
import 'package:mobile_scanner/mobile_scanner.dart';
void main() {
runApp(const AnalyzeView());
}
class AnalyzeView extends StatefulWidget {
const AnalyzeView({Key? key}) : super(key: key);
@override
_AnalyzeViewState createState() => _AnalyzeViewState();
}
class _AnalyzeViewState extends State<AnalyzeView>
with SingleTickerProviderStateMixin {
List<Offset> points = [];
// CameraController cameraController = CameraController(context, width: 320, height: 150);
String? barcode = null;
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: Builder(builder: (context) {
return Stack(
children: [
MobileScanner(
// fitScreen: false,
// controller: cameraController,
onDetect: (barcode, args) {
if (this.barcode != barcode.rawValue) {
this.barcode = barcode.rawValue;
if (barcode.corners != null) {
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
content: Text('${barcode.rawValue}'),
duration: const Duration(milliseconds: 200),
animation: null,
));
setState(() {
final List<Offset> points = [];
double factorWidth = args.size.width / 520;
// double factorHeight = wanted / args.size.height;
final size = MediaQuery.of(context).devicePixelRatio;
debugPrint('Size: ${barcode.corners}');
for (var point in barcode.corners!) {
final adjustedWith = point.dx ;
final adjustedHeight= point.dy ;
points.add(Offset(adjustedWith / size, adjustedHeight / size));
// points.add(Offset((point.dx ) / size,
// (point.dy) / size));
// final differenceWidth = (args.wantedSize!.width - args.size.width) / 2;
// final differenceHeight = (args.wantedSize!.height - args.size.height) / 2;
// points.add(Offset((point.dx + differenceWidth) / size,
// (point.dy + differenceHeight) / size));
}
this.points = points;
});
}
}
// Default 640 x480
}),
CustomPaint(
painter: OpenPainter(points),
),
// Container(
// alignment: Alignment.bottomCenter,
// margin: EdgeInsets.only(bottom: 80.0),
// child: IconButton(
// icon: ValueListenableBuilder(
// valueListenable: cameraController.torchState,
// builder: (context, state, child) {
// final color =
// state == TorchState.off ? Colors.grey : Colors.white;
// return Icon(Icons.bolt, color: color);
// },
// ),
// iconSize: 32.0,
// onPressed: () => cameraController.torch(),
// ),
// ),
],
);
}),
),
);
}
@override
void dispose() {
// cameraController.dispose();
super.dispose();
}
void display(Barcode barcode) {
Navigator.of(context).popAndPushNamed('display', arguments: barcode);
}
}
class OpenPainter extends CustomPainter {
final List<Offset> points;
OpenPainter(this.points);
@override
void paint(Canvas canvas, Size size) {
var paint1 = Paint()
..color = Color(0xff63aa65)
..strokeWidth = 10;
//draw points on canvas
canvas.drawPoints(PointMode.points, points, paint1);
}
@override
bool shouldRepaint(CustomPainter oldDelegate) => true;
}
class OpacityCurve extends Curve {
@override
double transform(double t) {
if (t < 0.1) {
return t * 10;
} else if (t <= 0.9) {
return 1.0;
} else {
return (1.0 - t) * 10;
}
}
}
// import 'package:flutter/material.dart';
// import 'package:flutter/rendering.dart';
// import 'package:mobile_scanner/mobile_scanner.dart';
//
// void main() {
// debugPaintSizeEnabled = false;
// runApp(HomePage());
// }
//
// class HomePage extends StatefulWidget {
// @override
// HomeState createState() => HomeState();
// }
//
// class HomeState extends State<HomePage> {
// @override
// Widget build(BuildContext context) {
// return MaterialApp(home: MyApp());
// }
// }
//
// class MyApp extends StatefulWidget {
// @override
// _MyAppState createState() => _MyAppState();
// }
//
// class _MyAppState extends State<MyApp> {
// String? qr;
// bool camState = false;
//
// @override
// initState() {
// super.initState();
// }
//
// @override
// Widget build(BuildContext context) {
// return Scaffold(
// appBar: AppBar(
// title: Text('Plugin example app'),
// ),
// body: Center(
// child: Column(
// crossAxisAlignment: CrossAxisAlignment.center,
// mainAxisAlignment: MainAxisAlignment.center,
// children: <Widget>[
// Expanded(
// child: camState
// ? Center(
// child: SizedBox(
// width: 300.0,
// height: 600.0,
// child: MobileScanner(
// onError: (context, error) => Text(
// error.toString(),
// style: TextStyle(color: Colors.red),
// ),
// qrCodeCallback: (code) {
// setState(() {
// qr = code;
// });
// },
// child: Container(
// decoration: BoxDecoration(
// color: Colors.transparent,
// border: Border.all(
// color: Colors.orange,
// width: 10.0,
// style: BorderStyle.solid),
// ),
// ),
// ),
// ),
// )
// : Center(child: Text("Camera inactive"))),
// Text("QRCODE: $qr"),
// ],
// ),
// ),
// floatingActionButton: FloatingActionButton(
// child: Text(
// "press me",
// textAlign: TextAlign.center,
// ),
// onPressed: () {
// setState(() {
// camState = !camState;
// });
// }),
// );
// }
// }
... ...
... ... @@ -3,18 +3,22 @@ import 'package:mobile_scanner/mobile_scanner.dart';
import 'mobile_scanner_arguments.dart';
enum Ratio {
ratio_4_3,
ratio_16_9
}
/// A widget showing a live camera preview.
class MobileScanner extends StatefulWidget {
/// The controller of the camera.
final MobileScannerController? controller;
final Function(Barcode barcode, MobileScannerArguments args)? onDetect;
final bool fitScreen;
final bool fitWidth;
final BoxFit fit;
/// Create a [MobileScanner] with a [controller], the [controller] must has been initialized.
const MobileScanner(
{Key? key, this.onDetect, this.controller, this.fitScreen = true, this.fitWidth = true})
: super(key: key);
{Key? key, this.onDetect, this.controller, this.fit = BoxFit.cover})
: assert((controller != null )), super(key: key);
@override
State<MobileScanner> createState() => _MobileScannerState();
... ... @@ -23,19 +27,28 @@ class MobileScanner extends StatefulWidget {
class _MobileScannerState extends State<MobileScanner>
with WidgetsBindingObserver {
bool onScreen = true;
MobileScannerController? controller;
late MobileScannerController controller;
@override
void initState() {
super.initState();
if (widget.controller == null) {
controller = MobileScannerController();
} else {
controller = widget.controller!;
}
}
@override
void didChangeAppLifecycleState(AppLifecycleState state) {
if (state == AppLifecycleState.resumed) {
setState(() => onScreen = true);
} else {
if (controller != null && onScreen) {
controller!.stop();
if (onScreen) {
controller.stop();
}
setState(() {
onScreen = false;
controller = null;
});
}
}
... ... @@ -43,28 +56,28 @@ class _MobileScannerState extends State<MobileScanner>
@override
Widget build(BuildContext context) {
return LayoutBuilder(builder: (context, BoxConstraints constraints) {
final media = MediaQuery.of(context);
controller ??= MobileScannerController(context,
width: constraints.maxWidth, height: constraints.maxHeight);
if (!onScreen) return const Text("Camera Paused.");
return ValueListenableBuilder(
valueListenable: controller!.args,
valueListenable: controller.args,
builder: (context, value, child) {
value = value as MobileScannerArguments?;
if (value == null) {
return Container(color: Colors.black);
} else {
controller!.barcodes.listen(
controller.barcodes.listen(
(a) => widget.onDetect!(a, value as MobileScannerArguments));
// Texture(textureId: value.textureId)
debugPrint(' size MediaQuery ${MediaQuery.of(context).size}');
return ClipRect(
child: FittedBox(
fit: BoxFit.cover,
child: SizedBox(
width: value.size.width,
height: value.size.height,
child: Texture(textureId: value.textureId),
child: SizedBox(
width: MediaQuery.of(context).size.width,
height: MediaQuery.of(context).size.height,
child: FittedBox(
fit: widget.fit,
child: SizedBox(
width: value.size.width,
height: value.size.height,
child: Texture(textureId: value.textureId),
),
),
),
);
... ... @@ -75,17 +88,7 @@ class _MobileScannerState extends State<MobileScanner>
@override
void dispose() {
controller?.dispose();
controller.dispose();
super.dispose();
}
}
extension on Size {
double fill(Size targetSize) {
if (targetSize.aspectRatio < aspectRatio) {
return targetSize.height * aspectRatio / targetSize.width;
} else {
return targetSize.width / aspectRatio / targetSize.height;
}
}
}
}
\ No newline at end of file
... ...
... ... @@ -8,9 +8,8 @@ class MobileScannerArguments {
/// Size of the texture.
final Size size;
/// Size of the texture.
final Size? wantedSize;
final bool hasTorch;
/// Create a [MobileScannerArguments].
MobileScannerArguments({required this.textureId,required this.size, this.wantedSize});
MobileScannerArguments({required this.textureId,required this.size, required this.hasTorch});
}
... ...
... ... @@ -2,9 +2,9 @@ import 'dart:async';
import 'package:flutter/cupertino.dart';
import 'package:flutter/services.dart';
import 'package:mobile_scanner/mobile_scanner.dart';
import 'mobile_scanner_arguments.dart';
import 'objects/barcode.dart';
import 'objects/barcode_utility.dart';
/// The facing of a camera.
... ... @@ -16,11 +16,7 @@ enum CameraFacing {
back,
}
enum MobileScannerState {
undetermined,
authorized,
denied
}
enum MobileScannerState { undetermined, authorized, denied }
/// The state of torch.
enum TorchState {
... ... @@ -31,85 +27,52 @@ enum TorchState {
on,
}
// /// A camera controller.
// abstract class CameraController {
// /// Arguments for [CameraView].
// ValueNotifier<CameraArgs?> get args;
//
// /// Torch state of the camera.
// ValueNotifier<TorchState> get torchState;
//
// /// A stream of barcodes.
// Stream<Barcode> get barcodes;
//
// /// Create a [CameraController].
// ///
// /// [facing] target facing used to select camera.
// ///
// /// [formats] the barcode formats for image analyzer.
// factory CameraController([CameraFacing facing = CameraFacing.back] ) =>
// _CameraController(facing);
//
// /// Start the camera asynchronously.
// Future<void> start();
//
// /// Switch the torch's state.
// void torch();
//
// /// Release the resources of the camera.
// void dispose();
// }
enum AnalyzeMode { none, barcode }
class MobileScannerController {
MethodChannel methodChannel =
const MethodChannel('dev.steenbakker.mobile_scanner/scanner/method');
EventChannel eventChannel =
const EventChannel('dev.steenbakker.mobile_scanner/scanner/event');
static const MethodChannel method =
MethodChannel('dev.steenbakker.mobile_scanner/scanner/method');
static const EventChannel event =
EventChannel('dev.steenbakker.mobile_scanner/scanner/event');
static const analyze_none = 0;
static const analyze_barcode = 1;
int? _controllerHashcode;
StreamSubscription? events;
static int? id;
static StreamSubscription? subscription;
final CameraFacing facing;
final ValueNotifier<MobileScannerArguments?> args;
final ValueNotifier<TorchState> torchState;
final ValueNotifier<MobileScannerArguments?> args = ValueNotifier(null);
final ValueNotifier<TorchState> torchState = ValueNotifier(TorchState.off);
late final ValueNotifier<CameraFacing> cameraFacingState;
final Ratio? ratio;
final bool? torchEnabled;
bool torchable;
CameraFacing facing;
bool hasTorch = false;
late StreamController<Barcode> barcodesController;
Stream<Barcode> get barcodes => barcodesController.stream;
MobileScannerController(BuildContext context, {required num width, required num height, this.facing = CameraFacing.back})
: args = ValueNotifier(null),
torchState = ValueNotifier(TorchState.off),
torchable = false {
// In case new instance before dispose.
if (id != null) {
MobileScannerController(
{this.facing = CameraFacing.back, this.ratio, this.torchEnabled}) {
// In case a new instance is created before calling dispose()
if (_controllerHashcode != null) {
stop();
}
id = hashCode;
// Create barcode stream controller.
_controllerHashcode = hashCode;
cameraFacingState = ValueNotifier(facing);
// Sets analyze mode and barcode stream
barcodesController = StreamController.broadcast(
onListen: () => tryAnalyze(analyze_barcode),
onCancel: () => tryAnalyze(analyze_none),
onListen: () => setAnalyzeMode(AnalyzeMode.barcode.index),
onCancel: () => setAnalyzeMode(AnalyzeMode.none.index),
);
final devicePixelRatio = MediaQuery.of(context).devicePixelRatio;
start();
start(
width: (devicePixelRatio * width.toInt()).ceil(),
height: (devicePixelRatio * height.toInt()).ceil());
// Listen event handler.
subscription =
event.receiveBroadcastStream().listen((data) => handleEvent(data));
// Listen to events from the platform specific code
events = eventChannel
.receiveBroadcastStream()
.listen((data) => handleEvent(data));
}
void handleEvent(Map<dynamic, dynamic> event) {
... ... @@ -129,76 +92,91 @@ class MobileScannerController {
}
}
void tryAnalyze(int mode) {
if (hashCode != id) {
void setAnalyzeMode(int mode) {
if (hashCode != _controllerHashcode) {
return;
}
method.invokeMethod('analyze', mode);
methodChannel.invokeMethod('analyze', mode);
}
Future<void> start({
int? width,
int? height,
// List<BarcodeFormats>? formats = _defaultBarcodeFormats,
}) async {
// List<BarcodeFormats>? formats = _defaultBarcodeFormats,
/// Start barcode scanning. This will first check if the required permissions
/// are set.
Future<void> start() async {
ensure('startAsync');
// Check authorization state.
MobileScannerState state = MobileScannerState.values[await method.invokeMethod('state')];
setAnalyzeMode(AnalyzeMode.barcode.index);
// Check authorization status
MobileScannerState state = MobileScannerState.values[await methodChannel.invokeMethod('state')];
switch (state) {
case MobileScannerState.undetermined:
final bool result = await method.invokeMethod('request');
state = result ? MobileScannerState.authorized : MobileScannerState.denied;
break;
case MobileScannerState.authorized:
final bool result = await methodChannel.invokeMethod('request');
state =
result ? MobileScannerState.authorized : MobileScannerState.denied;
break;
case MobileScannerState.denied:
throw PlatformException(code: 'NO ACCESS');
case MobileScannerState.authorized:
break;
}
debugPrint('TARGET RESOLUTION $width, $height');
// Start camera.
final answer =
await method.invokeMapMethod<String, dynamic>('start', {
'targetWidth': width,
'targetHeight': height,
'facing': facing.index
});
final textureId = answer?['textureId'];
final Size size = toSize(answer?['size']);
debugPrint('RECEIVED SIZE: ${size.width} ${size.height}');
if (width != null && height != null) {
args.value = MobileScannerArguments(textureId: textureId, size: size, wantedSize: Size(width.toDouble(), height.toDouble()));
} else {
args.value = MobileScannerArguments(textureId: textureId, size: size);
}
cameraFacingState.value = facing;
// Set the starting arguments for the camera
Map arguments = {};
arguments['facing'] = facing.index;
if (ratio != null) arguments['ratio'] = ratio;
if (torchEnabled != null) arguments['torch'] = torchEnabled;
// Start the camera with arguments
final Map<String, dynamic>? startResult = await methodChannel.invokeMapMethod<String, dynamic>(
'start', arguments);
torchable = answer?['torchable'];
if (startResult == null) throw PlatformException(code: 'INITIALIZATION ERROR');
hasTorch = startResult['torchable'];
args.value = MobileScannerArguments(textureId: startResult['textureId'], size: toSize(startResult['size']), hasTorch: hasTorch);
}
void torch() {
ensure('torch');
if (!torchable) return;
var state =
Future<void> stop() async => await methodChannel.invokeMethod('stop');
/// Switches the torch on or off.
///
/// Only works if torch is available.
void toggleTorch() {
ensure('toggleTorch');
if (!hasTorch) return;
TorchState state =
torchState.value == TorchState.off ? TorchState.on : TorchState.off;
method.invokeMethod('torch', state.index);
methodChannel.invokeMethod('torch', state.index);
}
/// Switches the torch on or off.
///
/// Only works if torch is available.
Future<void> switchCamera() async {
ensure('switchCamera');
await stop();
facing = facing == CameraFacing.back ? CameraFacing.front : CameraFacing.back;
start();
}
/// Disposes the controller and closes all listeners.
void dispose() {
if (hashCode == id) {
if (hashCode == _controllerHashcode) {
stop();
subscription?.cancel();
subscription = null;
id = null;
events?.cancel();
events = null;
_controllerHashcode = null;
}
barcodesController.close();
}
void stop() => method.invokeMethod('stop');
/// Checks if the controller is bound to the correct MobileScanner object.
void ensure(String name) {
final message =
'CameraController.$name called after CameraController.dispose\n'
'CameraController methods should not be used after calling dispose.';
assert(hashCode == id, message);
assert(hashCode == _controllerHashcode, message);
}
}
... ...
name: mobile_scanner
description: An universal scanner for Flutter based on MLKit.
description: An universal scanner for Flutter based on MLKit. Uses CameraX on Android and AVFoundation on iOS.
version: 0.0.1
homepage:
repository: https://github.com/juliansteenbakker/mobile_scanner
environment:
sdk: ">=2.16.0 <3.0.0"
... ...