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
Navaron Bracke
2023-11-08 16:51:05 +0100
Browse Files
Options
Browse Files
Download
Email Patches
Plain Diff
Commit
bf320d595a34d7335ba0b80c8ac364fd4fc44782
bf320d59
1 parent
8c72717b
reimplement parts of the MobileScannerController using the platform interface
Show whitespace changes
Inline
Side-by-side
Showing
1 changed file
with
93 additions
and
425 deletions
lib/src/mobile_scanner_controller.dart
lib/src/mobile_scanner_controller.dart
View file @
bf320d5
import
'dart:async'
;
import
'dart:io'
;
// ignore: unnecessary_import
import
'dart:typed_data'
;
import
'package:flutter/foundation.dart'
;
import
'package:flutter/services.dart'
;
...
...
@@ -12,53 +9,45 @@ import 'package:mobile_scanner/src/enums/mobile_scanner_error_code.dart';
import
'package:mobile_scanner/src/enums/mobile_scanner_state.dart'
;
import
'package:mobile_scanner/src/enums/torch_state.dart'
;
import
'package:mobile_scanner/src/mobile_scanner_exception.dart'
;
import
'package:mobile_scanner/src/
objects/barcod
e.dart'
;
import
'package:mobile_scanner/src/
mobile_scanner_platform_interfac
e.dart'
;
import
'package:mobile_scanner/src/objects/barcode_capture.dart'
;
import
'package:mobile_scanner/src/objects/mobile_scanner_arguments.dart'
;
/// The [MobileScannerController] holds all the logic of this plugin,
/// where as the [MobileScanner] class is the frontend of this plugin.
class
MobileScannerController
{
/// The controller for the [MobileScanner] widget.
class
MobileScannerController
extends
ValueNotifier
<
MobileScannerState
>
{
/// Construct a new [MobileScannerController] instance.
MobileScannerController
({
this
.
facing
=
CameraFacing
.
back
,
this
.
cameraResolution
,
this
.
detectionSpeed
=
DetectionSpeed
.
normal
,
this
.
detectionTimeoutMs
=
250
,
this
.
torchEnabled
=
false
,
this
.
formats
,
int
detectionTimeoutMs
=
250
,
this
.
facing
=
CameraFacing
.
back
,
this
.
formats
=
const
<
BarcodeFormat
>[],
this
.
returnImage
=
false
,
@Deprecated
(
'Instead, use the result of calling `start()` to determine if permissions were granted.'
,
)
this
.
onPermissionSet
,
this
.
autoStart
=
true
,
this
.
cameraResolution
,
this
.
torchEnabled
=
false
,
this
.
useNewCameraSelector
=
false
,
});
})
:
detectionTimeoutMs
=
detectionSpeed
==
DetectionSpeed
.
normal
?
detectionTimeoutMs
:
0
,
assert
(
detectionTimeoutMs
>=
0
,
'The detection timeout must be greater than or equal to 0.'
),
super
(
MobileScannerState
.
uninitialized
(
facing
));
///
Select which camera should be used
.
///
The desired resolution for the camera
.
///
/// Default: CameraFacing.back
final
CameraFacing
facing
;
/// Enable or disable the torch (Flash) on start
/// When this value is provided, the camera will try to match this resolution,
/// or fallback to the closest available resolution.
/// When this is null, Android defaults to a resolution of 640x480.
///
/// Default: disabled
final
bool
torchEnabled
;
/// Set to true if you want to return the image buffer with the Barcode event
/// Bear in mind that changing the resolution has an effect on the aspect ratio.
///
/// Only supported on iOS and Android
final
bool
returnImage
;
/// If provided, the scanner will only detect those specific formats
final
List
<
BarcodeFormat
>?
formats
;
/// When the camera orientation changes,
/// the resolution will be flipped to match the new dimensions of the display.
///
/// Currently only supported on Android.
final
Size
?
cameraResolution
;
///
Sets the speed of detections
.
///
The detection speed for the scanner
.
///
///
WARNING: DetectionSpeed.unrestricted can cause memory issues on some devices
///
Defaults to [DetectionSpeed.normal].
final
DetectionSpeed
detectionSpeed
;
///
Sets the timeout, in milliseconds, of
the scanner.
///
The detection timeout, in milliseconds, for
the scanner.
///
/// This timeout is ignored if the [detectionSpeed]
/// is not set to [DetectionSpeed.normal].
...
...
@@ -67,444 +56,123 @@ class MobileScannerController {
/// which prevents memory issues on older devices.
final
int
detectionTimeoutMs
;
/// Automatically start the mobileScanner on initialization.
final
bool
autoStart
;
/// The facing direction for the camera.
///
/// Defaults to the back-facing camera.
final
CameraFacing
facing
;
/// The
desired resolution for the camera
.
/// The
formats that the scanner should detect
.
///
/// When this value is provided, the camera will try to match this resolution,
/// or fallback to the closest available resolution.
/// When this is null, Android defaults to a resolution of 640x480.
/// If this is empty, all supported formats are detected.
final
List
<
BarcodeFormat
>
formats
;
/// Whether scanned barcodes should contain the image
/// that is embedded into the barcode.
///
///
Bear in mind that changing the resolution has an effect on the aspect ratio
.
///
If this is false, [BarcodeCapture.image] will always be null
.
///
/// When the camera orientation changes,
/// the resolution will be flipped to match the new dimensions of the display.
/// Defaults to false, and is only supported on iOS and Android.
final
bool
returnImage
;
/// Whether the flashlight should be turned on when the camera is started.
///
/// Currently only supported on Android.
final
Size
?
cameraResolution
;
/// Defaults to false.
final
bool
torchEnabled
;
/// Use the new resolution selector. Warning: not fully tested, may produce
/// unwanted/zoomed images.
/// Use the new resolution selector.
///
/// This feature is experimental and not fully tested yet.
/// Use caution when using this flag,
/// as the new resolution selector may produce unwanted or zoomed images.
///
/// Only supported on Android
/// Only supported on Android
.
final
bool
useNewCameraSelector
;
/// Sets the barcode stream
final
StreamController
<
BarcodeCapture
>
_barcodesController
=
StreamController
.
broadcast
();
/// The internal barcode controller, that listens for detected barcodes.
final
StreamController
<
BarcodeCapture
>
_barcodesController
=
StreamController
.
broadcast
();
/// Get the stream of scanned barcodes.
Stream
<
BarcodeCapture
>
get
barcodes
=>
_barcodesController
.
stream
;
static
const
MethodChannel
_methodChannel
=
MethodChannel
(
'dev.steenbakker.mobile_scanner/scanner/method'
);
static
const
EventChannel
_eventChannel
=
EventChannel
(
'dev.steenbakker.mobile_scanner/scanner/event'
);
@Deprecated
(
'Instead, use the result of calling `start()` to determine if permissions were granted.'
,
)
Function
(
bool
permissionGranted
)?
onPermissionSet
;
/// Listen to events from the platform specific code
StreamSubscription
?
events
;
/// A notifier that provides several arguments about the MobileScanner
final
ValueNotifier
<
MobileScannerArguments
?>
startArguments
=
ValueNotifier
(
null
);
/// A notifier that provides the state of the Torch (Flash)
final
ValueNotifier
<
TorchState
>
torchState
=
ValueNotifier
(
TorchState
.
off
);
/// A notifier that provides the state of which camera is being used
late
final
ValueNotifier
<
CameraFacing
>
cameraFacingState
=
ValueNotifier
(
facing
);
/// A notifier that provides zoomScale.
final
ValueNotifier
<
double
>
zoomScaleState
=
ValueNotifier
(
0.0
);
bool
isStarting
=
false
;
/// A notifier that provides availability of the Torch (Flash)
final
ValueNotifier
<
bool
?>
hasTorchState
=
ValueNotifier
(
false
);
/// Returns whether the device has a torch.
/// Analyze an image file.
///
/// Throws an error if the controller is not initialized.
bool
get
hasTorch
{
final
hasTorch
=
hasTorchState
.
value
;
if
(
hasTorch
==
null
)
{
throw
const
MobileScannerException
(
errorCode:
MobileScannerErrorCode
.
controllerUninitialized
,
);
}
return
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
.
rawValue
;
arguments
[
'torch'
]
=
torchEnabled
;
arguments
[
'speed'
]
=
detectionSpeed
.
rawValue
;
arguments
[
'timeout'
]
=
detectionTimeoutMs
;
arguments
[
'returnImage'
]
=
returnImage
;
arguments
[
'useNewCameraSelector'
]
=
useNewCameraSelector
;
/* if (scanWindow != null) {
arguments['scanWindow'] = [
scanWindow!.left,
scanWindow!.top,
scanWindow!.right,
scanWindow!.bottom,
];
} */
if
(
formats
!=
null
)
{
if
(
kIsWeb
||
Platform
.
isIOS
||
Platform
.
isMacOS
||
Platform
.
isAndroid
)
{
arguments
[
'formats'
]
=
formats
!.
map
((
e
)
=>
e
.
rawValue
).
toList
();
}
}
if
(
cameraResolution
!=
null
)
{
arguments
[
'cameraResolution'
]
=
<
int
>[
cameraResolution
!.
width
.
toInt
(),
cameraResolution
!.
height
.
toInt
(),
];
}
return
arguments
;
}
/// Start scanning for barcodes.
/// Upon calling this method, the necessary camera permission will be requested.
/// The [path] points to a file on the device.
///
/// Returns an instance of [MobileScannerArguments]
/// when the scanner was successfully started.
/// Returns null if the scanner is currently starting.
/// This is only supported on Android and iOS.
///
/// Throws a [MobileScannerException] if starting the scanner failed.
Future
<
MobileScannerArguments
?>
start
({
CameraFacing
?
cameraFacingOverride
,
})
async
{
if
(
isStarting
)
{
debugPrint
(
"Called start() while starting."
);
return
null
;
/// Returns the [BarcodeCapture] that was found in the image.
Future
<
BarcodeCapture
?>
analyzeImage
(
String
path
)
{
return
MobileScannerPlatform
.
instance
.
analyzeImage
(
path
);
}
events
??=
_eventChannel
.
receiveBroadcastStream
()
.
listen
((
data
)
=>
_handleEvent
(
data
as
Map
));
isStarting
=
true
;
// Check authorization status
if
(!
kIsWeb
)
{
final
MobileScannerState
state
;
try
{
state
=
MobileScannerState
.
fromRawValue
(
await
_methodChannel
.
invokeMethod
(
'state'
)
as
int
?
??
0
,
);
}
on
PlatformException
catch
(
error
)
{
isStarting
=
false
;
throw
MobileScannerException
(
errorCode:
MobileScannerErrorCode
.
genericError
,
errorDetails:
MobileScannerErrorDetails
(
code:
error
.
code
,
details:
error
.
details
as
Object
?,
message:
error
.
message
,
),
);
/// Reset the zoom scale of the camera.
Future
<
void
>
resetZoomScale
()
async
{
await
MobileScannerPlatform
.
instance
.
resetZoomScale
();
}
switch
(
state
)
{
// Android does not have an undetermined permission state.
// So if the permission state is denied, just request it now.
case
MobileScannerState
.
undetermined
:
case
MobileScannerState
.
denied
:
try
{
final
bool
granted
=
await
_methodChannel
.
invokeMethod
(
'request'
)
as
bool
?
??
false
;
if
(!
granted
)
{
isStarting
=
false
;
/// Set the zoom scale of the camera.
///
/// The [zoomScale] must be between 0.0 and 1.0 (both inclusive).
Future
<
void
>
setZoomScale
(
double
zoomScale
)
async
{
if
(
zoomScale
<
0
||
zoomScale
>
1
)
{
throw
const
MobileScannerException
(
errorCode:
MobileScannerErrorCode
.
permissionDenied
,
);
}
}
on
PlatformException
catch
(
error
)
{
isStarting
=
false
;
throw
MobileScannerException
(
errorCode:
MobileScannerErrorCode
.
genericError
,
errorDetails:
MobileScannerErrorDetails
(
code:
error
.
code
,
details:
error
.
details
as
Object
?,
message:
error
.
message
,
message:
'The zoomScale must be between 0.0 and 1.0'
,
),
);
}
case
MobileScannerState
.
authorized
:
break
;
}
await
MobileScannerPlatform
.
instance
.
setZoomScale
(
zoomScale
);
}
// Start the camera with arguments
Map
<
String
,
dynamic
>?
startResult
=
{};
try
{
startResult
=
await
_methodChannel
.
invokeMapMethod
<
String
,
dynamic
>(
'start'
,
_argumentsToMap
(
cameraFacingOverride:
cameraFacingOverride
),
);
}
on
PlatformException
catch
(
error
)
{
MobileScannerErrorCode
errorCode
=
MobileScannerErrorCode
.
genericError
;
final
String
?
errorMessage
=
error
.
message
;
if
(
kIsWeb
)
{
if
(
errorMessage
==
null
)
{
errorCode
=
MobileScannerErrorCode
.
genericError
;
}
else
if
(
errorMessage
.
contains
(
'NotFoundError'
)
||
errorMessage
.
contains
(
'NotSupportedError'
))
{
errorCode
=
MobileScannerErrorCode
.
unsupported
;
}
else
if
(
errorMessage
.
contains
(
'NotAllowedError'
))
{
errorCode
=
MobileScannerErrorCode
.
permissionDenied
;
}
else
{
errorCode
=
MobileScannerErrorCode
.
genericError
;
}
}
isStarting
=
false
;
throw
MobileScannerException
(
errorCode:
errorCode
,
errorDetails:
MobileScannerErrorDetails
(
code:
error
.
code
,
details:
error
.
details
as
Object
?,
message:
error
.
message
,
),
);
}
/// Stop the camera.
///
/// After calling this method, the camera can be restarted using [start].
Future
<
void
>
stop
()
async
{
await
MobileScannerPlatform
.
instance
.
stop
();
if
(
startResult
==
null
)
{
isStarting
=
false
;
throw
const
MobileScannerException
(
errorCode:
MobileScannerErrorCode
.
genericError
,
);
// After the camera stopped, set the torch state to off,
// as the torch state callback is never called when the camera is stopped.
torchState
.
value
=
TorchState
.
off
;
}
final
hasTorch
=
startResult
[
'torchable'
]
as
bool
?
??
false
;
hasTorchState
.
value
=
hasTorch
;
final
Size
size
;
if
(
kIsWeb
)
{
size
=
Size
(
startResult
[
'videoWidth'
]
as
double
?
??
0
,
startResult
[
'videoHeight'
]
as
double
?
??
0
,
);
}
else
{
final
Map
<
Object
?,
Object
?>?
sizeInfo
=
startResult
[
'size'
]
as
Map
<
Object
?,
Object
?>?;
size
=
Size
(
sizeInfo
?[
'width'
]
as
double
?
??
0
,
sizeInfo
?[
'height'
]
as
double
?
??
0
,
);
}
/// Switch between the front and back camera.
Future
<
void
>
switchCamera
()
async
{
await
MobileScannerPlatform
.
instance
.
stop
();
isStarting
=
false
;
return
startArguments
.
value
=
MobileScannerArguments
(
numberOfCameras:
startResult
[
'numberOfCameras'
]
as
int
?,
size:
size
,
hasTorch:
hasTorch
,
textureId:
kIsWeb
?
null
:
startResult
[
'textureId'
]
as
int
?,
webId:
kIsWeb
?
startResult
[
'ViewID'
]
as
String
?
:
null
,
);
}
final
CameraFacing
cameraDirection
;
/// Stops the camera, but does not dispose this controller.
Future
<
void
>
stop
()
async
{
await
_methodChannel
.
invokeMethod
(
'stop'
);
// TODO: update the camera facing direction state
// After the camera stopped, set the torch state to off,
// as the torch state callback is never called when the camera is stopped.
torchState
.
value
=
TorchState
.
off
;
await
start
(
cameraDirection:
cameraDirection
);
}
/// Switches the
torch
on or off.
/// Switches the
flashlight
on or off.
///
/// Does nothing if the device has no torch.
///
/// Throws if the controller was not initialized.
Future
<
void
>
toggleTorch
()
async
{
final
hasTorch
=
hasTorchState
.
value
;
if
(
hasTorch
==
null
)
{
throw
const
MobileScannerException
(
errorCode:
MobileScannerErrorCode
.
controllerUninitialized
,
);
}
final
bool
hasTorch
;
if
(!
hasTorch
)
{
return
;
}
final
TorchState
newState
=
torchState
.
value
==
TorchState
.
off
?
TorchState
.
on
:
TorchState
.
off
;
await
_methodChannel
.
invokeMethod
(
'torch'
,
newState
.
rawValue
);
}
final
TorchState
newState
=
torchState
.
value
==
TorchState
.
off
?
TorchState
.
on
:
TorchState
.
off
;
/// Changes the state of the camera (front or back).
///
/// Does nothing if the device has no front camera.
Future
<
void
>
switchCamera
()
async
{
await
_methodChannel
.
invokeMethod
(
'stop'
);
final
CameraFacing
facingToUse
=
cameraFacingState
.
value
==
CameraFacing
.
back
?
CameraFacing
.
front
:
CameraFacing
.
back
;
await
start
(
cameraFacingOverride:
facingToUse
);
// Update the torch state to the new state.
// When the platform has updated the torch state,
// it will send an update through the torch state event stream.
await
MobileScannerPlatform
.
instance
.
setTorchState
();
}
/// Handles a local image file.
/// Returns true if a barcode or QR code is found.
/// Returns false if nothing is found.
///
/// [path] The path of the image on the devices
Future
<
bool
>
analyzeImage
(
String
path
)
async
{
events
??=
_eventChannel
.
receiveBroadcastStream
()
.
listen
((
data
)
=>
_handleEvent
(
data
as
Map
));
return
_methodChannel
.
invokeMethod
<
bool
>(
'analyzeImage'
,
path
)
.
then
<
bool
>((
bool
?
value
)
=>
value
??
false
);
}
/// Set the zoomScale of the camera.
///
/// [zoomScale] must be within 0.0 and 1.0, where 1.0 is the max zoom, and 0.0
/// is zoomed out.
Future
<
void
>
setZoomScale
(
double
zoomScale
)
async
{
if
(
zoomScale
<
0
||
zoomScale
>
1
)
{
throw
const
MobileScannerException
(
errorCode:
MobileScannerErrorCode
.
genericError
,
errorDetails:
MobileScannerErrorDetails
(
message:
'The zoomScale must be between 0 and 1.'
,
),
);
}
await
_methodChannel
.
invokeMethod
(
'setScale'
,
zoomScale
);
}
/// Reset the zoomScale of the camera to use standard scale 1x.
Future
<
void
>
resetZoomScale
()
async
{
await
_methodChannel
.
invokeMethod
(
'resetScale'
);
}
/// Disposes the MobileScannerController and closes all listeners.
///
/// If you call this, you cannot use this controller object anymore.
void
dispose
()
{
stop
();
events
?.
cancel
();
_barcodesController
.
close
();
}
/// 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
;
case
'zoomScaleState'
:
zoomScaleState
.
value
=
data
as
double
?
??
0.0
;
case
'barcode'
:
if
(
data
==
null
)
return
;
final
parsed
=
(
data
as
List
)
.
map
((
value
)
=>
Barcode
.
fromNative
(
value
as
Map
))
.
toList
();
final
double
?
width
=
event
[
'width'
]
as
double
?;
final
double
?
height
=
event
[
'height'
]
as
double
?;
_barcodesController
.
add
(
BarcodeCapture
(
raw:
data
,
barcodes:
parsed
,
image:
event
[
'image'
]
as
Uint8List
?,
size:
width
==
null
||
height
==
null
?
Size
.
zero
:
Size
(
width
,
height
),
),
);
case
'barcodeMac'
:
_barcodesController
.
add
(
BarcodeCapture
(
raw:
data
,
barcodes:
[
Barcode
(
rawValue:
(
data
as
Map
)[
'payload'
]
as
String
?,
format:
BarcodeFormat
.
fromRawValue
(
data
[
'symbology'
]
as
int
?
??
-
1
,
),
),
],
),
);
case
'barcodeWeb'
:
final
barcode
=
data
as
Map
?;
final
corners
=
barcode
?[
'corners'
]
as
List
<
Object
?>?
??
<
Object
?>[];
_barcodesController
.
add
(
BarcodeCapture
(
raw:
data
,
barcodes:
[
if
(
barcode
!=
null
)
Barcode
(
rawValue:
barcode
[
'rawValue'
]
as
String
?,
rawBytes:
barcode
[
'rawBytes'
]
as
Uint8List
?,
format:
BarcodeFormat
.
fromRawValue
(
barcode
[
'format'
]
as
int
?
??
-
1
,
),
corners:
List
.
unmodifiable
(
corners
.
cast
<
Map
<
Object
?,
Object
?>>().
map
(
(
Map
<
Object
?,
Object
?>
e
)
{
return
Offset
(
e
[
'x'
]!
as
double
,
e
[
'y'
]!
as
double
);
},
),
),
),
],
),
);
case
'error'
:
throw
MobileScannerException
(
errorCode:
MobileScannerErrorCode
.
genericError
,
errorDetails:
MobileScannerErrorDetails
(
message:
data
as
String
?),
);
default
:
throw
UnimplementedError
(
name
as
String
?);
}
}
/// updates the native ScanWindow
Future
<
void
>
updateScanWindow
(
Rect
?
window
)
async
{
List
?
data
;
if
(
window
!=
null
)
{
data
=
[
window
.
left
,
window
.
top
,
window
.
right
,
window
.
bottom
];
}
@override
Future
<
void
>
dispose
()
async
{
await
MobileScannerPlatform
.
instance
.
dispose
();
unawaited
(
_barcodesController
.
close
());
await
_methodChannel
.
invokeMethod
(
'updateScanWindow'
,
{
'rect'
:
data
}
);
super
.
dispose
(
);
}
}
...
...
Please
register
or
login
to post a comment