Toggle navigation
Toggle navigation
This project
Loading...
Sign in
flutter_package
/
mobile_scanner
Go to a project
Toggle navigation
Projects
Groups
Snippets
Help
Toggle navigation pinning
Project
Activity
Repository
Pipelines
Graphs
Issues
0
Merge Requests
0
Wiki
Network
Create a new issue
Builds
Commits
Authored by
Julian Steenbakker
2022-10-31 21:32:39 +0100
Browse Files
Options
Browse Files
Download
Email Patches
Plain Diff
Commit
aa8298a78b88bfd46ebc02b5bdab680eb75a7f1a
aa8298a7
1 parent
448f15a2
feat: add return image and refactor existing functions
Show whitespace changes
Inline
Side-by-side
Showing
16 changed files
with
612 additions
and
273 deletions
android/build.gradle
android/src/main/kotlin/dev/steenbakker/mobile_scanner/MobileScanner.kt
example/ios/Runner.xcodeproj/project.pbxproj
example/lib/barcode_list_scanner_controller.dart
example/lib/barcode_scanner_controller.dart
example/lib/barcode_scanner_returning_image.dart
example/lib/barcode_scanner_without_controller.dart
example/lib/main.dart
lib/mobile_scanner.dart
lib/src/objects/barcode.dart → lib/src/barcode.dart
lib/src/barcode_capture.dart
lib/src/objects/barcode_utility.dart → lib/src/barcode_utility.dart
lib/src/mobile_scanner.dart
lib/src/mobile_scanner_controller.dart
lib/src/mobile_scanner_exception.dart
pubspec.yaml
android/build.gradle
View file @
aa8298a
...
...
@@ -50,7 +50,7 @@ dependencies {
// Use this dependency to bundle the model with your app
implementation
'com.google.mlkit:barcode-scanning:17.0.2'
// Use this dependency to use the dynamically downloaded model in Google Play Services
// implementation 'com.google.android.gms:play-services-mlkit-barcode-scanning:18.
0
.0'
// implementation 'com.google.android.gms:play-services-mlkit-barcode-scanning:18.
1
.0'
implementation
'androidx.camera:camera-camera2:1.1.0'
implementation
'androidx.camera:camera-lifecycle:1.1.0'
...
...
android/src/main/kotlin/dev/steenbakker/mobile_scanner/MobileScanner.kt
View file @
aa8298a
...
...
@@ -3,7 +3,11 @@ package dev.steenbakker.mobile_scanner
import android.Manifest
import android.app.Activity
import android.content.pm.PackageManager
import android.graphics.ImageFormat
import android.graphics.Point
import android.graphics.Rect
import android.graphics.YuvImage
import android.media.Image
import android.net.Uri
import android.util.Log
import android.util.Size
...
...
@@ -23,11 +27,13 @@ import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel
import io.flutter.plugin.common.PluginRegistry
import io.flutter.view.TextureRegistry
import java.io.ByteArrayOutputStream
import java.io.File
class MobileScanner(private val activity: Activity, private val textureRegistry: TextureRegistry)
: MethodChannel.MethodCallHandler, EventChannel.StreamHandler, PluginRegistry.RequestPermissionsResultListener {
class MobileScanner(private val activity: Activity, private val textureRegistry: TextureRegistry) :
MethodChannel.MethodCallHandler, EventChannel.StreamHandler,
PluginRegistry.RequestPermissionsResultListener {
companion object {
/**
* When the application's activity is [androidx.fragment.app.FragmentActivity], requestCode can only use the lower 16 bits.
...
...
@@ -70,14 +76,22 @@ class MobileScanner(private val activity: Activity, private val textureRegistry:
sink = null
}
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray): Boolean {
override fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array<out String>,
grantResults: IntArray
): Boolean {
return listener?.onRequestPermissionsResult(requestCode, permissions, grantResults) ?: false
}
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
if (ContextCompat.checkSelfPermission(
activity,
Manifest.permission.CAMERA
) == PackageManager.PERMISSION_GRANTED
) 1
else 0
result.success(state)
}
...
...
@@ -96,32 +110,83 @@ class MobileScanner(private val activity: Activity, private val textureRegistry:
val permissions = arrayOf(Manifest.permission.CAMERA)
ActivityCompat.requestPermissions(activity, permissions, REQUEST_CODE)
}
// var lastScanned: List<Barcode>? = null
// var isAnalyzing: Boolean = false
@ExperimentalGetImage
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)
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() }
// if (isAnalyzing) {
// Log.d("scanner", "SKIPPING" )
// return@addOnSuccessListener
// }
// isAnalyzing = true
val barcodeMap = barcodes.map { barcode -> barcode.data }
if (barcodeMap.isNotEmpty()) {
sink?.success(mapOf(
"name" to "barcode",
"data" to barcodeMap,
"image" to mediaImage.toByteArray()
))
}
// for (barcode in barcodes) {
//// if (lastScanned?.contains(barcodes.first) == true) continue;
// if (lastScanned == null) {
// lastScanned = barcodes
// } else if (lastScanned!!.contains(barcode)) {
// // Duplicate, don't send image
// sink?.success(mapOf(
// "name" to "barcode",
// "data" to barcode.data,
// ))
// } else {
// if (byteArray.isEmpty()) {
// Log.d("scanner", "EMPTY" )
// return@addOnSuccessListener
// }
// else -> imageProxy.close()
//
// Log.d("scanner", "SCANNED IMAGE: $byteArray")
// lastScanned = barcodes;
//
//
// }
//
// }
// isAnalyzing = false
}
.addOnFailureListener { e -> sink?.success(mapOf(
"name" to "error",
"data" to e.localizedMessage
)) }
.addOnCompleteListener { imageProxy.close() }
}
private fun Image.toByteArray(): ByteArray {
val yBuffer = planes[0].buffer // Y
val vuBuffer = planes[2].buffer // VU
val ySize = yBuffer.remaining()
val vuSize = vuBuffer.remaining()
val nv21 = ByteArray(ySize + vuSize)
yBuffer.get(nv21, 0, ySize)
vuBuffer.get(nv21, ySize, vuSize)
val yuvImage = YuvImage(nv21, ImageFormat.NV21, this.width, this.height, null)
val out = ByteArrayOutputStream()
yuvImage.compressToJpeg(Rect(0, 0, yuvImage.width, yuvImage.height), 50, out)
return out.toByteArray()
}
private var scanner = BarcodeScanning.getClient()
@ExperimentalGetImage
private fun start(call: MethodCall, result: MethodChannel.Result) {
if (camera?.cameraInfo != null && preview != null && textureEntry != null) {
...
...
@@ -129,14 +194,23 @@ class MobileScanner(private val activity: Activity, private val textureRegistry:
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)
val answer = mapOf("textureId" to textureEntry!!.id(), "size" to size, "torchable" to camera!!.cameraInfo.hasFlashUnit())
val size = if (portrait) mapOf(
"width" to width,
"height" to height
) else mapOf("width" to height, "height" to width)
val answer = mapOf(
"textureId" to textureEntry!!.id(),
"size" to size,
"torchable" to camera!!.cameraInfo.hasFlashUnit()
)
result.success(answer)
} else {
val facing: Int = call.argument<Int>("facing") ?: 0
val ratio: Int? = call.argument<Int>("ratio")
val torch: Boolean = call.argument<Boolean>("torch") ?: false
val formats: List<Int>? = call.argument<List<Int>>("formats")
// val analyzerWidth = call.argument<Int>("ratio")
// val analyzeRHEIG = call.argument<Int>("ratio")
if (formats != null) {
val formatsList: MutableList<Int> = mutableListOf()
...
...
@@ -144,9 +218,17 @@ class MobileScanner(private val activity: Activity, private val textureRegistry:
formatsList.add(BarcodeFormats.values()[index].intValue)
}
scanner = if (formatsList.size == 1) {
BarcodeScanning.getClient(BarcodeScannerOptions.Builder().setBarcodeFormats(formatsList.first()).build())
BarcodeScanning.getClient(
BarcodeScannerOptions.Builder().setBarcodeFormats(formatsList.first())
.build()
)
} else {
BarcodeScanning.getClient(BarcodeScannerOptions.Builder().setBarcodeFormats(formatsList.first(), *formatsList.subList(1, formatsList.size).toIntArray()).build())
BarcodeScanning.getClient(
BarcodeScannerOptions.Builder().setBarcodeFormats(
formatsList.first(),
*formatsList.subList(1, formatsList.size).toIntArray()
).build()
)
}
}
...
...
@@ -168,7 +250,10 @@ class MobileScanner(private val activity: Activity, private val textureRegistry:
// Preview
val surfaceProvider = Preview.SurfaceProvider { request ->
val texture = textureEntry!!.surfaceTexture()
texture.setDefaultBufferSize(request.resolution.width, request.resolution.height)
texture.setDefaultBufferSize(
request.resolution.width,
request.resolution.height
)
val surface = Surface(texture)
request.provideSurface(surface, executor) { }
}
...
...
@@ -186,12 +271,19 @@ class MobileScanner(private val activity: Activity, private val textureRegistry:
if (ratio != null) {
analysisBuilder.setTargetAspectRatio(ratio)
}
// analysisBuilder.setTargetResolution(Size(1440, 1920))
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
val selector =
if (facing == 0) CameraSelector.DEFAULT_FRONT_CAMERA else CameraSelector.DEFAULT_BACK_CAMERA
camera = cameraProvider!!.bindToLifecycle(activity as LifecycleOwner, selector, preview, analysis)
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)
...
...
@@ -216,8 +308,15 @@ class MobileScanner(private val activity: Activity, private val textureRegistry:
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)
val answer = mapOf("textureId" to textureEntry!!.id(), "size" to size, "torchable" to camera!!.cameraInfo.hasFlashUnit())
val size = if (portrait) mapOf(
"width" to width,
"height" to height
) else mapOf("width" to height, "height" to width)
val answer = mapOf(
"textureId" to textureEntry!!.id(),
"size" to size,
"torchable" to camera!!.cameraInfo.hasFlashUnit()
)
result.success(answer)
}, executor)
}
...
...
@@ -225,7 +324,7 @@ class MobileScanner(private val activity: Activity, private val textureRegistry:
private fun toggleTorch(call: MethodCall, result: MethodChannel.Result) {
if (camera == null) {
result.error(TAG,"Called toggleTorch() while stopped!", null)
result.error(TAG,
"Called toggleTorch() while stopped!", null)
return
}
camera!!.cameraControl.enableTorch(call.arguments == 1)
...
...
@@ -238,7 +337,7 @@ class MobileScanner(private val activity: Activity, private val textureRegistry:
// }
private fun analyzeImage(call: MethodCall, result: MethodChannel.Result) {
val uri = Uri.fromFile(
File(call.arguments.toString()))
val uri = Uri.fromFile(File(call.arguments.toString()))
val inputImage = InputImage.fromFilePath(activity, uri)
var barcodeFound = false
...
...
@@ -249,15 +348,17 @@ class MobileScanner(private val activity: Activity, private val textureRegistry:
sink?.success(mapOf("name" to "barcode", "data" to barcode.data))
}
}
.addOnFailureListener { e -> Log.e(TAG, e.message, e)
result.error(TAG, e.message, e)}
.addOnFailureListener { e ->
Log.e(TAG, e.message, e)
result.error(TAG, e.message, e)
}
.addOnCompleteListener { result.success(barcodeFound) }
}
private fun stop(result: MethodChannel.Result) {
if (camera == null && preview == null) {
result.error(TAG,"Called stop() while already stopped!", null)
result.error(TAG,
"Called stop() while already stopped!", null)
return
}
...
...
@@ -277,41 +378,54 @@ class MobileScanner(private val activity: Activity, private val textureRegistry:
private val Barcode.data: Map<String, Any?>
get() = mapOf("corners" to cornerPoints?.map { corner -> corner.data }, "format" to format,
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, "displayValue" to displayValue)
"url" to url?.data, "wifi" to wifi?.data, "displayValue" to displayValue
)
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,
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)
"summary" to summary
)
private val Barcode.ContactInfo.data: Map<String, Any?>
get() = mapOf("addresses" to addresses.map { address -> address.data },
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)
"title" to title, "urls" to urls
)
private val Barcode.Address.data: Map<String, Any?>
get() = mapOf("addressLines" to addressLines.map { addressLine -> addressLine.toString() }, "type" to type)
get() = mapOf(
"addressLines" to addressLines.map { addressLine -> addressLine.toString() },
"type" to type
)
private val Barcode.PersonName.data: Map<String, Any?>
get() = mapOf("first" to first, "formattedName" to formattedName, "last" to last,
get() = mapOf(
"first" to first, "formattedName" to formattedName, "last" to last,
"middle" to middle, "prefix" to prefix, "pronunciation" to pronunciation,
"suffix" to suffix)
"suffix" to suffix
)
private val Barcode.DriverLicense.data: Map<String, Any?>
get() = mapOf("addressCity" to addressCity, "addressState" to addressState,
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)
"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)
...
...
example/ios/Runner.xcodeproj/project.pbxproj
View file @
aa8298a
...
...
@@ -355,7 +355,7 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
DEVELOPMENT_TEAM =
RCH2VG82SH
;
DEVELOPMENT_TEAM =
75Y2P2WSQQ
;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
...
...
@@ -484,7 +484,7 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
DEVELOPMENT_TEAM =
RCH2VG82SH
;
DEVELOPMENT_TEAM =
75Y2P2WSQQ
;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
...
...
@@ -507,7 +507,7 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
DEVELOPMENT_TEAM =
RCH2VG82SH
;
DEVELOPMENT_TEAM =
75Y2P2WSQQ
;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
...
...
example/lib/barcode_list_scanner_controller.dart
0 → 100644
View file @
aa8298a
import
'package:flutter/material.dart'
;
import
'package:image_picker/image_picker.dart'
;
import
'package:mobile_scanner/mobile_scanner.dart'
;
class
BarcodeListScannerWithController
extends
StatefulWidget
{
const
BarcodeListScannerWithController
({
Key
?
key
})
:
super
(
key:
key
);
@override
_BarcodeListScannerWithControllerState
createState
()
=>
_BarcodeListScannerWithControllerState
();
}
class
_BarcodeListScannerWithControllerState
extends
State
<
BarcodeListScannerWithController
>
with
SingleTickerProviderStateMixin
{
BarcodeCapture
?
barcodeCapture
;
MobileScannerController
controller
=
MobileScannerController
(
torchEnabled:
true
,
// formats: [BarcodeFormat.qrCode]
// facing: CameraFacing.front,
);
bool
isStarted
=
true
;
@override
Widget
build
(
BuildContext
context
)
{
return
Scaffold
(
backgroundColor:
Colors
.
black
,
body:
Builder
(
builder:
(
context
)
{
return
Stack
(
children:
[
MobileScanner
(
controller:
controller
,
fit:
BoxFit
.
contain
,
// allowDuplicates: true,
// controller: MobileScannerController(
// torchEnabled: true,
// facing: CameraFacing.front,
// ),
onDetect:
(
barcodeCapture
,
arguments
)
{
setState
(()
{
this
.
barcodeCapture
=
barcodeCapture
;
});
},
),
Align
(
alignment:
Alignment
.
bottomCenter
,
child:
Container
(
alignment:
Alignment
.
bottomCenter
,
height:
100
,
color:
Colors
.
black
.
withOpacity
(
0.4
),
child:
Row
(
mainAxisAlignment:
MainAxisAlignment
.
spaceEvenly
,
children:
[
IconButton
(
color:
Colors
.
white
,
icon:
ValueListenableBuilder
(
valueListenable:
controller
.
torchState
,
builder:
(
context
,
state
,
child
)
{
if
(
state
==
null
)
{
return
const
Icon
(
Icons
.
flash_off
,
color:
Colors
.
grey
,
);
}
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
(),
),
IconButton
(
color:
Colors
.
white
,
icon:
isStarted
?
const
Icon
(
Icons
.
stop
)
:
const
Icon
(
Icons
.
play_arrow
),
iconSize:
32.0
,
onPressed:
()
=>
setState
(()
{
isStarted
?
controller
.
stop
()
:
controller
.
start
();
isStarted
=
!
isStarted
;
}),
),
Center
(
child:
SizedBox
(
width:
MediaQuery
.
of
(
context
).
size
.
width
-
200
,
height:
50
,
child:
FittedBox
(
child:
Text
(
'
${barcodeCapture?.barcodes.map((e) => e.rawValue)}
'
,
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
==
null
)
{
return
const
Icon
(
Icons
.
camera_front
);
}
switch
(
state
as
CameraFacing
)
{
case
CameraFacing
.
front
:
return
const
Icon
(
Icons
.
camera_front
);
case
CameraFacing
.
back
:
return
const
Icon
(
Icons
.
camera_rear
);
}
},
),
iconSize:
32.0
,
onPressed:
()
=>
controller
.
switchCamera
(),
),
IconButton
(
color:
Colors
.
white
,
icon:
const
Icon
(
Icons
.
image
),
iconSize:
32.0
,
onPressed:
()
async
{
final
ImagePicker
picker
=
ImagePicker
();
// Pick an image
final
XFile
?
image
=
await
picker
.
pickImage
(
source
:
ImageSource
.
gallery
,
);
if
(
image
!=
null
)
{
if
(
await
controller
.
analyzeImage
(
image
.
path
))
{
if
(!
mounted
)
return
;
ScaffoldMessenger
.
of
(
context
).
showSnackBar
(
const
SnackBar
(
content:
Text
(
'Barcode found!'
),
backgroundColor:
Colors
.
green
,
),
);
}
else
{
if
(!
mounted
)
return
;
ScaffoldMessenger
.
of
(
context
).
showSnackBar
(
const
SnackBar
(
content:
Text
(
'No barcode found!'
),
backgroundColor:
Colors
.
red
,
),
);
}
}
},
),
],
),
),
),
],
);
},
),
);
}
}
\ No newline at end of file
...
...
example/lib/barcode_scanner_controller.dart
View file @
aa8298a
...
...
@@ -13,10 +13,12 @@ class BarcodeScannerWithController extends StatefulWidget {
class
_BarcodeScannerWithControllerState
extends
State
<
BarcodeScannerWithController
>
with
SingleTickerProviderStateMixin
{
String
?
barcode
;
BarcodeCapture
?
barcode
;
MobileScannerController
controller
=
MobileScannerController
(
torchEnabled:
true
,
detectionSpeed:
DetectionSpeed
.
unrestricted
// formats: [BarcodeFormat.qrCode]
// facing: CameraFacing.front,
);
...
...
@@ -41,7 +43,7 @@ class _BarcodeScannerWithControllerState
// ),
onDetect:
(
barcode
,
args
)
{
setState
(()
{
this
.
barcode
=
barcode
.
rawValue
;
this
.
barcode
=
barcode
;
});
},
),
...
...
@@ -99,7 +101,7 @@ class _BarcodeScannerWithControllerState
height:
50
,
child:
FittedBox
(
child:
Text
(
barcode
??
'Scan something!'
,
barcode
?.
barcodes
.
first
.
rawValue
??
'Scan something!'
,
overflow:
TextOverflow
.
fade
,
style:
Theme
.
of
(
context
)
.
textTheme
...
...
example/lib/barcode_scanner_returning_image.dart
View file @
aa8298a
import
'dart:math'
;
import
'dart:typed_data'
;
import
'package:flutter/material.dart'
;
...
...
@@ -14,11 +15,11 @@ class BarcodeScannerReturningImage extends StatefulWidget {
class
_BarcodeScannerReturningImageState
extends
State
<
BarcodeScannerReturningImage
>
with
SingleTickerProviderStateMixin
{
String
?
barcode
;
Uint8List
?
image
;
BarcodeCapture
?
barcode
;
MobileScannerArguments
?
arguments
;
MobileScannerController
controller
=
MobileScannerController
(
torchEnabled:
true
,
//
torchEnabled: true,
returnImage:
true
,
// formats: [BarcodeFormat.qrCode]
// facing: CameraFacing.front,
...
...
@@ -26,13 +27,34 @@ class _BarcodeScannerReturningImageState
bool
isStarted
=
true
;
@override
Widget
build
(
BuildContext
context
)
{
return
Scaffold
(
backgroundColor:
Colors
.
black
,
body:
Builder
(
builder:
(
context
)
{
return
Stack
(
return
Column
(
children:
[
Container
(
color:
Colors
.
blueGrey
,
width:
double
.
infinity
,
height:
0.33
*
MediaQuery
.
of
(
context
).
size
.
height
,
child:
barcode
?.
image
!=
null
?
Transform
.
rotate
(
angle:
90
*
pi
/
180
,
child:
Image
(
gaplessPlayback:
true
,
image:
MemoryImage
(
barcode
!.
image
!),
fit:
BoxFit
.
contain
,
),
)
:
Container
(
color:
Colors
.
white
,
child:
const
Center
(
child:
Text
(
'Your scanned barcode will appear here!'
))),
),
Container
(
height:
0.66
*
MediaQuery
.
of
(
context
).
size
.
height
,
color:
Colors
.
grey
,
child:
Stack
(
children:
[
MobileScanner
(
controller:
controller
,
...
...
@@ -42,17 +64,10 @@ class _BarcodeScannerReturningImageState
// torchEnabled: true,
// facing: CameraFacing.front,
// ),
onDetect:
(
barcode
,
arg
s
)
{
onDetect:
(
barcode
,
argument
s
)
{
setState
(()
{
this
.
barcode
=
barcode
.
rawValue
;
showDialog
(
context:
context
,
builder:
(
context
)
=>
Image
(
image:
MemoryImage
(
image
!),
fit:
BoxFit
.
contain
,
),
);
image
=
barcode
.
image
;
this
.
arguments
=
arguments
;
this
.
barcode
=
barcode
;
});
},
),
...
...
@@ -65,8 +80,10 @@ class _BarcodeScannerReturningImageState
child:
Row
(
mainAxisAlignment:
MainAxisAlignment
.
spaceEvenly
,
children:
[
IconButton
(
color:
Colors
.
white
,
Container
(
color:
arguments
!=
null
&&
!
arguments
!.
hasTorch
?
Colors
.
red
:
Colors
.
white
,
child:
IconButton
(
// color: ,
icon:
ValueListenableBuilder
(
valueListenable:
controller
.
torchState
,
builder:
(
context
,
state
,
child
)
{
...
...
@@ -93,6 +110,7 @@ class _BarcodeScannerReturningImageState
iconSize:
32.0
,
onPressed:
()
=>
controller
.
toggleTorch
(),
),
),
IconButton
(
color:
Colors
.
white
,
icon:
isStarted
...
...
@@ -110,7 +128,7 @@ class _BarcodeScannerReturningImageState
height:
50
,
child:
FittedBox
(
child:
Text
(
barcod
e
??
'Scan something!'
,
barcode
?.
barcodes
.
first
.
rawValu
e
??
'Scan something!'
,
overflow:
TextOverflow
.
fade
,
style:
Theme
.
of
(
context
)
.
textTheme
...
...
@@ -139,21 +157,14 @@ class _BarcodeScannerReturningImageState
iconSize:
32.0
,
onPressed:
()
=>
controller
.
switchCamera
(),
),
SizedBox
(
width:
50
,
height:
50
,
child:
image
!=
null
?
Image
(
image:
MemoryImage
(
image
!),
fit:
BoxFit
.
contain
,
)
:
Container
(),
),
],
),
),
),
],
),
),
],
);
},
),
...
...
example/lib/barcode_scanner_without_controller.dart
View file @
aa8298a
...
...
@@ -12,7 +12,7 @@ class BarcodeScannerWithoutController extends StatefulWidget {
class
_BarcodeScannerWithoutControllerState
extends
State
<
BarcodeScannerWithoutController
>
with
SingleTickerProviderStateMixin
{
String
?
barcod
e
;
BarcodeCapture
?
captur
e
;
@override
Widget
build
(
BuildContext
context
)
{
...
...
@@ -25,9 +25,9 @@ class _BarcodeScannerWithoutControllerState
MobileScanner
(
fit:
BoxFit
.
contain
,
// allowDuplicates: false,
onDetect:
(
barcode
,
arg
s
)
{
onDetect:
(
capture
,
argument
s
)
{
setState
(()
{
this
.
barcode
=
barcode
.
rawValu
e
;
this
.
capture
=
captur
e
;
});
},
),
...
...
@@ -46,7 +46,7 @@ class _BarcodeScannerWithoutControllerState
height:
50
,
child:
FittedBox
(
child:
Text
(
barcod
e
??
'Scan something!'
,
capture
?.
barcodes
.
first
.
rawValu
e
??
'Scan something!'
,
overflow:
TextOverflow
.
fade
,
style:
Theme
.
of
(
context
)
.
textTheme
...
...
example/lib/main.dart
View file @
aa8298a
import
'package:flutter/material.dart'
;
import
'package:mobile_scanner_example/barcode_list_scanner_controller.dart'
;
import
'package:mobile_scanner_example/barcode_scanner_controller.dart'
;
import
'package:mobile_scanner_example/barcode_scanner_returning_image.dart'
;
import
'package:mobile_scanner_example/barcode_scanner_without_controller.dart'
;
...
...
@@ -22,6 +23,16 @@ class MyHome extends StatelessWidget {
onPressed:
()
{
Navigator
.
of
(
context
).
push
(
MaterialPageRoute
(
builder:
(
context
)
=>
const
BarcodeListScannerWithController
(),
),
);
},
child:
const
Text
(
'MobileScanner with List Controller'
),
),
ElevatedButton
(
onPressed:
()
{
Navigator
.
of
(
context
).
push
(
MaterialPageRoute
(
builder:
(
context
)
=>
const
BarcodeScannerWithController
(),
),
);
...
...
lib/mobile_scanner.dart
View file @
aa8298a
library
mobile_scanner
;
export
'src/barcode.dart'
;
export
'src/barcode_capture.dart'
;
export
'src/enums/camera_facing.dart'
;
export
'src/enums/detection_speed.dart'
;
export
'src/enums/mobile_scanner_state.dart'
;
...
...
@@ -8,4 +10,3 @@ export 'src/enums/torch_state.dart';
export
'src/mobile_scanner.dart'
;
export
'src/mobile_scanner_arguments.dart'
;
export
'src/mobile_scanner_controller.dart'
;
export
'src/objects/barcode.dart'
;
...
...
lib/src/
objects/
barcode.dart → lib/src/barcode.dart
View file @
aa8298a
import
'dart:typed_data'
;
import
'dart:ui'
;
import
'package:mobile_scanner/src/
objects/
barcode_utility.dart'
;
import
'package:mobile_scanner/src/barcode_utility.dart'
;
/// Represents a single recognized barcode and its value.
class
Barcode
{
...
...
@@ -97,7 +97,7 @@ class Barcode {
});
/// Create a [Barcode] from native data.
Barcode
.
fromNative
(
Map
data
,
this
.
image
)
Barcode
.
fromNative
(
Map
data
,
{
this
.
image
}
)
:
corners
=
toCorners
(
data
[
'corners'
]
as
List
?),
format
=
toFormat
(
data
[
'format'
]
as
int
),
rawBytes
=
data
[
'rawBytes'
]
as
Uint8List
?,
...
...
lib/src/barcode_capture.dart
0 → 100644
View file @
aa8298a
import
'dart:typed_data'
;
import
'package:mobile_scanner/src/barcode.dart'
;
class
BarcodeCapture
{
List
<
Barcode
>
barcodes
;
Uint8List
?
image
;
BarcodeCapture
({
required
this
.
barcodes
,
this
.
image
,
});
}
...
...
lib/src/
objects/
barcode_utility.dart → lib/src/barcode_utility.dart
View file @
aa8298a
lib/src/mobile_scanner.dart
View file @
aa8298a
import
'package:flutter/foundation.dart'
;
import
'package:flutter/material.dart'
;
import
'package:mobile_scanner/mobile_scanner.dart'
;
import
'package:mobile_scanner/src/mobile_scanner_arguments.dart'
;
import
'package:mobile_scanner/src/mobile_scanner_controller.dart'
;
import
'package:mobile_scanner/src/barcode_capture.dart'
;
/// A widget showing a live camera preview.
class
MobileScanner
extends
StatefulWidget
{
...
...
@@ -13,28 +15,24 @@ class MobileScanner extends StatefulWidget {
/// Function that gets called when a Barcode is detected.
///
/// [barcode] The barcode object with all information about the scanned code.
/// [args] Information about the state of the MobileScanner widget
final
Function
(
Barcode
barcode
,
MobileScannerArguments
?
args
)
onDetect
;
/// TODO: Function that gets called when the Widget is initialized. Can be usefull
/// to check wether the device has a torch(flash) or not.
///
/// [args] Information about the state of the MobileScanner widget
// final Function(MobileScannerArguments args)? onInitialize;
/// [startArguments] Information about the state of the MobileScanner widget
final
Function
(
BarcodeCapture
capture
,
MobileScannerArguments
?
arguments
)
onDetect
;
/// Handles how the widget should fit the screen.
final
BoxFit
fit
;
/// Set to false if you don't want duplicate scans.
final
bool
allowDuplicates
;
/// Whether to automatically resume the camera when the application is resumed
final
bool
autoResume
;
/// Create a [MobileScanner] with a [controller], the [controller] must has been initialized.
const
MobileScanner
({
super
.
key
,
required
this
.
onDetect
,
this
.
controller
,
this
.
autoResume
=
true
,
this
.
fit
=
BoxFit
.
cover
,
this
.
allowDuplicates
=
false
,
this
.
onPermissionSet
,
});
...
...
@@ -55,40 +53,35 @@ class _MobileScannerState extends State<MobileScanner>
if
(!
controller
.
isStarting
)
controller
.
start
();
}
AppLifecycleState
?
_lastState
;
@override
void
didChangeAppLifecycleState
(
AppLifecycleState
state
)
{
switch
(
state
)
{
case
AppLifecycleState
.
resumed
:
if
(!
controller
.
isStarting
&&
controller
.
autoResum
e
)
controller
.
start
();
if
(!
controller
.
isStarting
&&
widget
.
autoResume
&&
_lastState
!=
AppLifecycleState
.
inactiv
e
)
controller
.
start
();
break
;
case
AppLifecycleState
.
inactive
:
case
AppLifecycleState
.
paused
:
case
AppLifecycleState
.
detached
:
controller
.
stop
();
break
;
default
:
break
;
}
_lastState
=
state
;
}
Uint8List
?
lastScanned
;
@override
Widget
build
(
BuildContext
context
)
{
return
ValueListenableBuilder
(
valueListenable:
controller
.
arg
s
,
valueListenable:
controller
.
startArgument
s
,
builder:
(
context
,
value
,
child
)
{
value
=
value
as
MobileScannerArguments
?;
if
(
value
==
null
)
{
return
const
ColoredBox
(
color:
Colors
.
black
);
}
else
{
controller
.
barcodes
.
listen
((
barcode
)
{
if
(!
widget
.
allowDuplicates
)
{
if
(
lastScanned
!=
barcode
.
rawBytes
)
{
lastScanned
=
barcode
.
rawBytes
;
widget
.
onDetect
(
barcode
,
value
!
as
MobileScannerArguments
);
}
}
else
{
widget
.
onDetect
(
barcode
,
value
!
as
MobileScannerArguments
);
}
});
return
ClipRect
(
child:
SizedBox
(
...
...
lib/src/mobile_scanner_controller.dart
View file @
aa8298a
...
...
@@ -5,170 +5,155 @@ import 'package:flutter/cupertino.dart';
import
'package:flutter/foundation.dart'
;
import
'package:flutter/services.dart'
;
import
'package:mobile_scanner/mobile_scanner.dart'
;
import
'package:mobile_scanner/src/objects/barcode_utility.dart'
;
import
'package:mobile_scanner/src/barcode_capture.dart'
;
import
'package:mobile_scanner/src/barcode_utility.dart'
;
import
'package:mobile_scanner/src/mobile_scanner_exception.dart'
;
/// The [MobileScannerController] holds all the logic of this plugin,
/// where as the [MobileScanner] class is the frontend of this plugin.
class
MobileScannerController
{
MethodChannel
methodChannel
=
const
MethodChannel
(
'dev.steenbakker.mobile_scanner/scanner/method'
);
EventChannel
eventChannel
=
const
EventChannel
(
'dev.steenbakker.mobile_scanner/scanner/event'
);
MobileScannerController
({
this
.
facing
=
CameraFacing
.
back
,
this
.
detectionSpeed
=
DetectionSpeed
.
noDuplicates
,
// this.ratio,
this
.
torchEnabled
=
false
,
this
.
formats
,
// this.autoResume = true,
this
.
returnImage
=
false
,
this
.
onPermissionSet
,
})
{
// In case a new instance is created before calling dispose()
if
(
controllerHashcode
!=
null
)
{
stop
();
}
controllerHashcode
=
hashCode
;
events
=
_eventChannel
.
receiveBroadcastStream
()
.
listen
((
data
)
=>
_handleEvent
(
data
as
Map
));
}
//Must be static to keep the same value on new instances
static
int
?
_controllerHashcode
;
StreamSubscription
?
events
;
static
int
?
controllerHashcode
;
Function
(
bool
permissionGranted
)?
onPermissionSet
;
final
ValueNotifier
<
MobileScannerArguments
?>
args
=
ValueNotifier
(
null
);
final
ValueNotifier
<
TorchState
>
torchState
=
ValueNotifier
(
TorchState
.
off
);
late
final
ValueNotifier
<
CameraFacing
>
cameraFacingState
;
final
Ratio
?
ratio
;
final
bool
?
torchEnabled
;
// Whether to return the image buffer with the Barcode event
/// Select which camera should be used.
///
/// Default: CameraFacing.back
final
CameraFacing
facing
;
// /// Analyze the image in 4:3 or 16:9
// ///
// /// Only on Android
// final Ratio? ratio;
/// Enable or disable the torch (Flash) on start
///
/// Default: disabled
final
bool
torchEnabled
;
/// Set to true if you want to return the image buffer with the Barcode event
///
/// Only supported on iOS and Android
final
bool
returnImage
;
/// If provided, the scanner will only detect those specific formats
.
/// If provided, the scanner will only detect those specific formats
final
List
<
BarcodeFormat
>?
formats
;
CameraFacing
facing
;
bool
hasTorch
=
false
;
late
StreamController
<
Barcode
>
barcodesController
;
/// Whether to automatically resume the camera when the application is resumed
bool
autoResume
;
/// Sets the speed of detections.
///
/// WARNING: DetectionSpeed.unrestricted can cause memory issues on some devices
final
DetectionSpeed
detectionSpeed
;
Stream
<
Barcode
>
get
barcodes
=>
barcodesController
.
stream
;
/// Sets the barcode stream
final
StreamController
<
BarcodeCapture
>
_barcodesController
=
StreamController
.
broadcast
();
Stream
<
BarcodeCapture
>
get
barcodes
=>
_barcodesController
.
stream
;
MobileScannerController
({
this
.
facing
=
CameraFacing
.
back
,
this
.
ratio
,
this
.
torchEnabled
,
this
.
formats
,
this
.
onPermissionSet
,
this
.
autoResume
=
true
,
this
.
returnImage
=
false
,
})
{
// In case a new instance is created before calling dispose()
if
(
_controllerHashcode
!=
null
)
{
stop
();
}
_controllerHashcode
=
hashCode
;
static
const
MethodChannel
_methodChannel
=
MethodChannel
(
'dev.steenbakker.mobile_scanner/scanner/method'
);
static
const
EventChannel
_eventChannel
=
EventChannel
(
'dev.steenbakker.mobile_scanner/scanner/event'
);
cameraFacingState
=
ValueNotifier
(
facing
)
;
Function
(
bool
permissionGranted
)?
onPermissionSet
;
// Sets analyze mode and barcode stream
barcodesController
=
StreamController
.
broadcast
(
// onListen: () => setAnalyzeMode(AnalyzeMode.barcode.index),
// onCancel: () => setAnalyzeMode(AnalyzeMode.none.index),
);
/// Listen to events from the platform specific code
late
StreamSubscription
events
;
// Listen to events from the platform specific code
events
=
eventChannel
.
receiveBroadcastStream
()
.
listen
((
data
)
=>
handleEvent
(
data
as
Map
));
}
/// A notifier that provides several arguments about the MobileScanner
final
ValueNotifier
<
MobileScannerArguments
?>
startArguments
=
ValueNotifier
(
null
);
void
handleEvent
(
Map
event
)
{
final
name
=
event
[
'name'
];
final
data
=
event
[
'data'
];
final
binaryData
=
event
[
'binaryData'
];
switch
(
name
)
{
case
'torchState'
:
final
state
=
TorchState
.
values
[
data
as
int
?
??
0
];
torchState
.
value
=
state
;
break
;
case
'barcode'
:
final
image
=
returnImage
?
event
[
'image'
]
as
Uint8List
:
null
;
final
barcode
=
Barcode
.
fromNative
(
data
as
Map
?
??
{},
image
);
barcodesController
.
add
(
barcode
);
break
;
case
'barcodeMac'
:
barcodesController
.
add
(
Barcode
(
rawValue:
(
data
as
Map
)[
'payload'
]
as
String
?,
),
);
break
;
case
'barcodeWeb'
:
final
bytes
=
(
binaryData
as
List
).
cast
<
int
>();
barcodesController
.
add
(
Barcode
(
rawValue:
data
as
String
?,
rawBytes:
Uint8List
.
fromList
(
bytes
),
),
);
break
;
default
:
throw
UnimplementedError
();
}
}
/// A notifier that provides the state of the Torch (Flash)
final
ValueNotifier
<
TorchState
>
torchState
=
ValueNotifier
(
TorchState
.
off
);
// TODO: Add more analyzers like text analyzer
// void setAnalyzeMode(int mode) {
// if (hashCode != _controllerHashcode) {
// return;
// }
// methodChannel.invokeMethod('analyze', mode);
// }
/// A notifier that provides the state of which camera is being used
late
final
ValueNotifier
<
CameraFacing
>
cameraFacingState
=
ValueNotifier
(
facing
);
// List<BarcodeFormats>? formats = _defaultBarcodeFormats,
bool
isStarting
=
false
;
bool
?
_hasTorch
;
/// Set the starting arguments for the camera
Map
<
String
,
dynamic
>
_argumentsToMap
({
CameraFacing
?
cameraFacingOverride
})
{
final
Map
<
String
,
dynamic
>
arguments
=
{};
cameraFacingState
.
value
=
cameraFacingOverride
??
facing
;
arguments
[
'facing'
]
=
cameraFacingState
.
value
.
index
;
// if (ratio != null) arguments['ratio'] = ratio;
arguments
[
'torch'
]
=
torchEnabled
;
arguments
[
'speed'
]
=
detectionSpeed
.
index
;
if
(
formats
!=
null
)
{
if
(
Platform
.
isAndroid
)
{
arguments
[
'formats'
]
=
formats
!.
map
((
e
)
=>
e
.
index
).
toList
();
}
else
if
(
Platform
.
isIOS
||
Platform
.
isMacOS
)
{
arguments
[
'formats'
]
=
formats
!.
map
((
e
)
=>
e
.
rawValue
).
toList
();
}
}
arguments
[
'returnImage'
]
=
true
;
return
arguments
;
}
/// Start barcode scanning. This will first check if the required permissions
/// are set.
Future
<
void
>
start
()
async
{
ensure
(
'startAsync'
);
Future
<
MobileScannerArguments
?>
start
({
CameraFacing
?
cameraFacingOverride
,
})
async
{
debugPrint
(
'Hashcode controller:
$hashCode
'
);
if
(
isStarting
)
{
throw
Exception
(
'mobile_scanner: Called start() while already starting.'
);
debugPrint
(
"Called start() while starting."
);
}
isStarting
=
true
;
// setAnalyzeMode(AnalyzeMode.barcode.index);
// Check authorization status
if
(!
kIsWeb
)
{
MobileScannerState
state
=
MobileScannerState
.
values
[
await
methodChannel
.
invokeMethod
(
'state'
)
as
int
?
??
0
];
final
MobileScannerState
state
=
MobileScannerState
.
values
[
await
_methodChannel
.
invokeMethod
(
'state'
)
as
int
?
??
0
];
switch
(
state
)
{
case
MobileScannerState
.
undetermined
:
final
bool
result
=
await
methodChannel
.
invokeMethod
(
'request'
)
as
bool
?
??
false
;
state
=
result
?
MobileScannerState
.
authorized
:
MobileScannerState
.
denied
;
await
_methodChannel
.
invokeMethod
(
'request'
)
as
bool
?
??
false
;
if
(!
result
)
{
isStarting
=
false
;
onPermissionSet
?.
call
(
result
);
throw
MobileScannerException
(
'User declined camera permission.'
);
}
break
;
case
MobileScannerState
.
denied
:
isStarting
=
false
;
onPermissionSet
?.
call
(
false
);
throw
PlatformException
(
code:
'NO ACCESS
'
);
throw
MobileScannerException
(
'User declined camera permission.
'
);
case
MobileScannerState
.
authorized
:
onPermissionSet
?.
call
(
true
);
break
;
}
}
cameraFacingState
.
value
=
facing
;
// Set the starting arguments for the camera
final
Map
arguments
=
{};
arguments
[
'facing'
]
=
facing
.
index
;
if
(
ratio
!=
null
)
arguments
[
'ratio'
]
=
ratio
;
if
(
torchEnabled
!=
null
)
arguments
[
'torch'
]
=
torchEnabled
;
if
(
formats
!=
null
)
{
if
(
Platform
.
isAndroid
)
{
arguments
[
'formats'
]
=
formats
!.
map
((
e
)
=>
e
.
index
).
toList
();
}
else
if
(
Platform
.
isIOS
||
Platform
.
isMacOS
)
{
arguments
[
'formats'
]
=
formats
!.
map
((
e
)
=>
e
.
rawValue
).
toList
();
}
}
arguments
[
'returnImage'
]
=
returnImage
;
// Start the camera with arguments
Map
<
String
,
dynamic
>?
startResult
=
{};
try
{
startResult
=
await
methodChannel
.
invokeMapMethod
<
String
,
dynamic
>(
startResult
=
await
_
methodChannel
.
invokeMapMethod
<
String
,
dynamic
>(
'start'
,
arguments
,
_argumentsToMap
(
cameraFacingOverride:
cameraFacingOverride
)
,
);
}
on
PlatformException
catch
(
error
)
{
debugPrint
(
'
${error.code}
:
${error.message}
'
);
...
...
@@ -176,85 +161,76 @@ class MobileScannerController {
if
(
error
.
code
==
"MobileScannerWeb"
)
{
onPermissionSet
?.
call
(
false
);
}
// setAnalyzeMode(AnalyzeMode.none.index);
return
;
return
null
;
}
if
(
startResult
==
null
)
{
isStarting
=
false
;
throw
PlatformException
(
code:
'INITIALIZATION ERROR'
);
throw
MobileScannerException
(
'Failed to start mobileScanner, no response from platform side'
);
}
hasTorch
=
startResult
[
'torchable'
]
as
bool
?
??
false
;
_hasTorch
=
startResult
[
'torchable'
]
as
bool
?
??
false
;
if
(
_hasTorch
!
&&
torchEnabled
)
{
torchState
.
value
=
TorchState
.
on
;
}
if
(
kIsWeb
)
{
onPermissionSet
?.
call
(
true
,
);
// If we reach this line, it means camera permission has been granted
arg
s
.
value
=
MobileScannerArguments
(
startArgument
s
.
value
=
MobileScannerArguments
(
webId:
startResult
[
'ViewID'
]
as
String
?,
size:
Size
(
startResult
[
'videoWidth'
]
as
double
?
??
0
,
startResult
[
'videoHeight'
]
as
double
?
??
0
,
),
hasTorch:
hasTorch
,
hasTorch:
_hasTorch
!
,
);
}
else
{
arg
s
.
value
=
MobileScannerArguments
(
startArgument
s
.
value
=
MobileScannerArguments
(
textureId:
startResult
[
'textureId'
]
as
int
?,
size:
toSize
(
startResult
[
'size'
]
as
Map
?
??
{}),
hasTorch:
hasTorch
,
hasTorch:
_hasTorch
!
,
);
}
isStarting
=
false
;
return
startArguments
.
value
!;
}
/// Stops the camera, but does not dispose this controller.
Future
<
void
>
stop
()
async
{
try
{
await
methodChannel
.
invokeMethod
(
'stop'
);
}
on
PlatformException
catch
(
error
)
{
debugPrint
(
'
${error.code}
:
${error.message}
'
);
}
await
_methodChannel
.
invokeMethod
(
'stop'
);
}
/// Switches the torch on or off.
///
/// Only works if torch is available.
Future
<
void
>
toggleTorch
()
async
{
ensure
(
'toggleTorch'
);
if
(!
hasTorch
)
{
debugPrint
(
'Device has no torch/flash.'
);
return
;
if
(
_hasTorch
==
null
)
{
throw
MobileScannerException
(
'Cannot toggle torch if start() has never been called'
);
}
else
if
(!
_hasTorch
!)
{
throw
MobileScannerException
(
'Device has no torch'
);
}
final
TorchState
stat
e
=
torchState
.
valu
e
=
torchState
.
value
==
TorchState
.
off
?
TorchState
.
on
:
TorchState
.
off
;
try
{
await
methodChannel
.
invokeMethod
(
'torch'
,
state
.
index
);
}
on
PlatformException
catch
(
error
)
{
debugPrint
(
'
${error.code}
:
${error.message}
'
);
}
await
_methodChannel
.
invokeMethod
(
'torch'
,
torchState
.
value
.
index
);
}
/// Switches the torch on or off.
///
/// Only works if torch is available.
Future
<
void
>
switchCamera
()
async
{
ensure
(
'switchCamera'
);
try
{
await
methodChannel
.
invokeMethod
(
'stop'
);
}
on
PlatformException
catch
(
error
)
{
debugPrint
(
'
${error.code}
: camera is stopped! Please start before switching camera.'
,
);
return
;
}
facing
=
facing
==
CameraFacing
.
back
?
CameraFacing
.
front
:
CameraFacing
.
back
;
await
start
();
await
_methodChannel
.
invokeMethod
(
'stop'
);
final
CameraFacing
facingToUse
=
cameraFacingState
.
value
==
CameraFacing
.
back
?
CameraFacing
.
front
:
CameraFacing
.
back
;
await
start
(
cameraFacingOverride:
facingToUse
);
}
/// Handles a local image file.
...
...
@@ -263,28 +239,66 @@ class MobileScannerController {
///
/// [path] The path of the image on the devices
Future
<
bool
>
analyzeImage
(
String
path
)
async
{
return
methodChannel
return
_
methodChannel
.
invokeMethod
<
bool
>(
'analyzeImage'
,
path
)
.
then
<
bool
>((
bool
?
value
)
=>
value
??
false
);
}
/// Disposes the MobileScannerController and closes all listeners.
///
/// If you call this, you cannot use this controller object anymore.
void
dispose
()
{
if
(
hashCode
==
_controllerHashcode
)
{
stop
();
events
?.
cancel
();
events
=
null
;
_controllerHashcode
=
null
;
events
.
cancel
();
_barcodesController
.
close
();
if
(
hashCode
==
controllerHashcode
)
{
controllerHashcode
=
null
;
onPermissionSet
=
null
;
}
barcodesController
.
close
();
}
/// Checks if the MobileScannerController is bound to the correct MobileScanner object.
void
ensure
(
String
name
)
{
final
message
=
'MobileScannerController.
$name
called after MobileScannerController.dispose
\n
'
'MobileScannerController methods should not be used after calling dispose.'
;
assert
(
hashCode
==
_controllerHashcode
,
message
);
/// Handles a returning event from the platform side
void
_handleEvent
(
Map
event
)
{
final
name
=
event
[
'name'
];
final
data
=
event
[
'data'
];
switch
(
name
)
{
case
'torchState'
:
final
state
=
TorchState
.
values
[
data
as
int
?
??
0
];
torchState
.
value
=
state
;
break
;
case
'barcode'
:
if
(
data
==
null
)
return
;
final
parsed
=
(
data
as
List
)
.
map
((
value
)
=>
Barcode
.
fromNative
(
value
as
Map
))
.
toList
();
_barcodesController
.
add
(
BarcodeCapture
(
barcodes:
parsed
,
image:
event
[
'image'
]
as
Uint8List
,
));
break
;
case
'barcodeMac'
:
_barcodesController
.
add
(
BarcodeCapture
(
barcodes:
[
Barcode
(
rawValue:
(
data
as
Map
)[
'payload'
]
as
String
?,
)
],
),
);
break
;
case
'barcodeWeb'
:
_barcodesController
.
add
(
BarcodeCapture
(
barcodes:
[
Barcode
(
rawValue:
data
as
String
?,
)
]));
break
;
case
'error'
:
throw
MobileScannerException
(
data
as
String
);
default
:
throw
UnimplementedError
(
name
as
String
?);
}
}
}
\ No newline at end of file
...
...
lib/src/mobile_scanner_exception.dart
0 → 100644
View file @
aa8298a
class
MobileScannerException
implements
Exception
{
String
message
;
MobileScannerException
(
this
.
message
);
}
...
...
pubspec.yaml
View file @
aa8298a
...
...
@@ -12,11 +12,13 @@ dependencies:
sdk
:
flutter
flutter_web_plugins
:
sdk
:
flutter
js
:
^0.6.3
js
on_serializable
:
^6.3.1
dev_dependencies
:
build_runner
:
^2.2.0
flutter_test
:
sdk
:
flutter
json_annotation
:
^4.6.0
lint
:
^1.10.0
flutter
:
...
...
Please
register
or
login
to post a comment