Browse Source

Added possibility to choose facing camera on start.

Moved the size listener to plugin side.
flutter-beta
Julian Steenbakker 5 years ago
parent
commit
e6b74ac752
No known key found for this signature in database GPG Key ID: 16E437C7A3D94250
8 changed files with 211 additions and 143 deletions
  1. +17
    -6
      android/src/main/kotlin/net/touchcapture/qr/flutterqr/QRView.kt
  2. +3
    -2
      android/src/main/kotlin/net/touchcapture/qr/flutterqr/QRViewFactory.kt
  3. +1
    -0
      example/.gitignore
  4. +0
    -2
      example/ios/Runner.xcodeproj/project.pbxproj
  5. +12
    -19
      example/lib/main.dart
  6. +69
    -50
      ios/Classes/QRView.swift
  7. +2
    -2
      ios/Classes/QRViewFactory.swift
  8. +107
    -62
      lib/src/qr_code_scanner.dart

+ 17
- 6
android/src/main/kotlin/net/touchcapture/qr/flutterqr/QRView.kt View File

@@ -17,7 +17,7 @@ import io.flutter.plugin.common.BinaryMessenger
import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel
import io.flutter.plugin.platform.PlatformView
class QRView(messenger: BinaryMessenger, id: Int, private val context: Context) :
class QRView(messenger: BinaryMessenger, id: Int, private val context: Context, private val params: HashMap<String, Any>) :
PlatformView, MethodChannel.MethodCallHandler {

private var isTorchOn: Boolean = false
@@ -66,6 +66,12 @@ class QRView(messenger: BinaryMessenger, id: Int, private val context: Context)

override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) {
when(call.method){
"startScan" -> {
startScan()
}
"stopScan" -> {
stopScan()
}
"flipCamera" -> {
flipCamera()
}
@@ -127,14 +133,16 @@ class QRView(messenger: BinaryMessenger, id: Int, private val context: Context)

private fun initBarCodeView(): BarcodeView? {
if (barcodeView == null) {
barcodeView = createBarCodeView()
barcodeView = BarcodeView(Shared.activity)
if (params["cameraFacing"] as Int == 1) {
barcodeView?.cameraSettings?.requestedCameraId = CameraInfo.CAMERA_FACING_FRONT
}
}
return barcodeView
}

private fun createBarCodeView(): BarcodeView {
val barcode = BarcodeView(Shared.activity)
barcode.decodeContinuous(
private fun startScan() {
barcodeView?.decodeContinuous(
object : BarcodeCallback {
override fun barcodeResult(result: BarcodeResult) {
val code = mapOf(
@@ -147,7 +155,10 @@ class QRView(messenger: BinaryMessenger, id: Int, private val context: Context)
override fun possibleResultPoints(resultPoints: List<ResultPoint>) {}
}
)
return barcode
}

private fun stopScan() {
barcodeView?.stopDecoding()
}

private fun hasCameraPermission(): Boolean {


+ 3
- 2
android/src/main/kotlin/net/touchcapture/qr/flutterqr/QRViewFactory.kt View File

@@ -10,8 +10,9 @@ import io.flutter.plugin.platform.PlatformViewFactory
class QRViewFactory(private val messenger: BinaryMessenger) :
PlatformViewFactory(StandardMessageCodec.INSTANCE) {

override fun create(context: Context, id: Int, obj: Any?): PlatformView {
return QRView(messenger, id, context)
override fun create(context: Context, id: Int, args: Any?): PlatformView {
val params = args as HashMap<String, Any>
return QRView(messenger, id, context, params)
}

}

+ 1
- 0
example/.gitignore View File

@@ -9,6 +9,7 @@
.buildlog/
.history
.svn/
.last_build_id

# IntelliJ related
*.iml


+ 0
- 2
example/ios/Runner.xcodeproj/project.pbxproj View File

@@ -264,13 +264,11 @@
);
inputPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh",
"${PODS_ROOT}/../Flutter/Flutter.framework",
"${BUILT_PRODUCTS_DIR}/MTBBarcodeScanner/MTBBarcodeScanner.framework",
"${BUILT_PRODUCTS_DIR}/qr_code_scanner/qr_code_scanner.framework",
);
name = "[CP] Embed Pods Frameworks";
outputPaths = (
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Flutter.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/MTBBarcodeScanner.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/qr_code_scanner.framework",
);


+ 12
- 19
example/lib/main.dart View File

@@ -154,25 +154,18 @@ class _QRViewExampleState extends State<QRViewExample> {
: 300.0;
// To ensure the Scanner view is properly sizes after rotation
// we need to listen for Flutter SizeChanged notification and update controller
return NotificationListener<SizeChangedLayoutNotification>(
onNotification: (notification) {
Future.microtask(
() => controller?.updateDimensions(qrKey, scanArea: scanArea));
return false;
},
child: SizeChangedLayoutNotifier(
key: const Key('qr-size-notifier'),
child: QRView(
key: qrKey,
onQRViewCreated: _onQRViewCreated,
overlay: QrScannerOverlayShape(
borderColor: Colors.red,
borderRadius: 10,
borderLength: 30,
borderWidth: 10,
cutOutSize: scanArea,
),
)));
return QRView(
key: qrKey,
cameraFacing: CameraFacing.front,
onQRViewCreated: _onQRViewCreated,
overlay: QrScannerOverlayShape(
borderColor: Colors.red,
borderRadius: 10,
borderLength: 30,
borderWidth: 10,
cutOutSize: scanArea,
),
);
}

void _onQRViewCreated(QRViewController controller) {


+ 69
- 50
ios/Classes/QRView.swift View File

@@ -13,67 +13,28 @@ public class QRView:NSObject,FlutterPlatformView {
var scanner: MTBBarcodeScanner?
var registrar: FlutterPluginRegistrar
var channel: FlutterMethodChannel
var cameraFacing: MTBCamera
public init(withFrame frame: CGRect, withRegistrar registrar: FlutterPluginRegistrar, withId id: Int64){
public init(withFrame frame: CGRect, withRegistrar registrar: FlutterPluginRegistrar, withId id: Int64, params: Dictionary<String, Any>){
self.registrar = registrar
previewView = UIView(frame: frame)
cameraFacing = MTBCamera.init(rawValue: UInt(Int(params["cameraFacing"] as! Double))) ?? MTBCamera.back
channel = FlutterMethodChannel(name: "net.touchcapture.qr.flutterqr/qrview_\(id)", binaryMessenger: registrar.messenger())
}
func isCameraAvailable(success: Bool) -> Void {
if success {
do {
try scanner?.startScanning(resultBlock: { [weak self] codes in
if let codes = codes {
for code in codes {
var typeString: String;
switch(code.type) {
case AVMetadataObject.ObjectType.aztec:
typeString = "AZTEC"
case AVMetadataObject.ObjectType.code39:
typeString = "CODE_39"
case AVMetadataObject.ObjectType.code93:
typeString = "CODE_93"
case AVMetadataObject.ObjectType.code128:
typeString = "CODE_128"
case AVMetadataObject.ObjectType.dataMatrix:
typeString = "DATA_MATRIX"
case AVMetadataObject.ObjectType.ean8:
typeString = "EAN_8"
case AVMetadataObject.ObjectType.ean13:
typeString = "EAN_13"
case AVMetadataObject.ObjectType.itf14:
typeString = "ITF"
case AVMetadataObject.ObjectType.pdf417:
typeString = "PDF_417"
case AVMetadataObject.ObjectType.qr:
typeString = "QR_CODE"
case AVMetadataObject.ObjectType.upce:
typeString = "UPC_E"
default:
return
}
guard let stringValue = code.stringValue else { continue }
let result = ["code": stringValue, "type": typeString]
self?.channel.invokeMethod("onRecognizeQR", arguments: result)
}
}
})
} catch {
NSLog("Unable to start scanning")
}
} else {
UIAlertView(title: "Scanning Unavailable", message: "This app does not have permission to access the camera", delegate: nil, cancelButtonTitle: nil, otherButtonTitles: "Ok").show()
}
deinit {
scanner?.stopScanning()
}
public func view() -> UIView {
channel.setMethodCallHandler({
[weak self] (call: FlutterMethodCall, result: FlutterResult) -> Void in
[weak self] (call: FlutterMethodCall, result: @escaping FlutterResult) -> Void in
switch(call.method){
case "setDimensions":
let arguments = call.arguments as! Dictionary<String, Double>
self?.setDimensions(width: arguments["width"] ?? 0, height: arguments["height"] ?? 0, scanArea: arguments["scanArea"] ?? 0)
case "startScan":
self?.startScan(result)
case "flipCamera":
self?.flipCamera()
case "toggleFlash":
@@ -106,9 +67,67 @@ public class QRView:NSObject,FlutterPlatformView {
self.scanner?.scanRect = CGRect(x: Double(midX) - (scanArea / 2), y: Double(midY) - (scanArea / 2), width: scanArea, height: scanArea)
}
}


MTBBarcodeScanner.requestCameraPermission(success: isCameraAvailable)
}
}
func startScan(_ result: @escaping FlutterResult) -> Void {
scanner = MTBBarcodeScanner(previewView: previewView)
MTBBarcodeScanner.requestCameraPermission(success: { permissionGranted in
if permissionGranted {
do {
try self.scanner?.startScanning(with: self.cameraFacing, resultBlock: { [weak self] codes in
if let codes = codes {
for code in codes {
var typeString: String;
switch(code.type) {
case AVMetadataObject.ObjectType.aztec:
typeString = "AZTEC"
case AVMetadataObject.ObjectType.code39:
typeString = "CODE_39"
case AVMetadataObject.ObjectType.code93:
typeString = "CODE_93"
case AVMetadataObject.ObjectType.code128:
typeString = "CODE_128"
case AVMetadataObject.ObjectType.dataMatrix:
typeString = "DATA_MATRIX"
case AVMetadataObject.ObjectType.ean8:
typeString = "EAN_8"
case AVMetadataObject.ObjectType.ean13:
typeString = "EAN_13"
case AVMetadataObject.ObjectType.itf14:
typeString = "ITF"
case AVMetadataObject.ObjectType.pdf417:
typeString = "PDF_417"
case AVMetadataObject.ObjectType.qr:
typeString = "QR_CODE"
case AVMetadataObject.ObjectType.upce:
typeString = "UPC_E"
default:
return
}
guard let stringValue = code.stringValue else { continue }
let result = ["code": stringValue, "type": typeString]
self?.channel.invokeMethod("onRecognizeQR", arguments: result)
}
}
})
} catch {
let error = FlutterError(code: "unknown-error", message: "Unable to start scanning", details: nil)
result(error)
}
} else {
let error = FlutterError(code: "cameraPermission", message: "Permission denied to access the camera", details: nil)
result(error)
}
})
}
func stopScan(){
if let sc: MTBBarcodeScanner = scanner {
if sc.isScanning() {
sc.stopScanning()
}
}
}


+ 2
- 2
ios/Classes/QRViewFactory.swift View File

@@ -17,8 +17,8 @@ public class QRViewFactory: NSObject, FlutterPlatformViewFactory {
}
public func create(withFrame frame: CGRect, viewIdentifier viewId: Int64, arguments args: Any?) -> FlutterPlatformView {
let dictionary = args as! Dictionary<String, Double>
return QRView(withFrame: CGRect(x: 0, y: 0, width: dictionary["width"] ?? 0, height: dictionary["height"] ?? 0), withRegistrar: registrar!,withId: viewId)
let params = args as! Dictionary<String, Double>
return QRView(withFrame: frame, withRegistrar: registrar!,withId: viewId, params: params)
}
public func createArgsCodec() -> FlutterMessageCodec & NSObjectProtocol {


+ 107
- 62
lib/src/qr_code_scanner.dart View File

@@ -7,6 +7,14 @@ import 'package:qr_code_scanner/qr_code_scanner.dart';

typedef QRViewCreatedCallback = void Function(QRViewController);

enum CameraFacing {
/// Shows back facing camera.
back,

/// Shows front facing camera.
front
}

enum BarcodeFormat {
/// Aztec 2D barcode format.
aztec,
@@ -80,6 +88,11 @@ const _formatNames = <String, BarcodeFormat>{
'UPC_EAN_EXTENSION': BarcodeFormat.upcEanExtension,
};

/// The [Barcode] object holds information about the barcode or qr code.
///
/// [code] is the content of the barcode.
/// [format] displays which type the code is.
/// Only for Android, [rawBytes] gives a list of bytes of the result.
class Barcode {
Barcode(this.code, this.format, this.rawBytes);

@@ -90,60 +103,84 @@ class Barcode {
final List<int> rawBytes;
}

/// The [QRView] is the view where the camera and the barcode scanner gets displayed.
class QRView extends StatefulWidget {
const QRView({
@required Key key,
@required this.onQRViewCreated,
this.overlay,
this.overlayMargin = EdgeInsets.zero,
this.cameraFacing = CameraFacing.back,
}) : assert(key != null),
assert(onQRViewCreated != null),
super(key: key);

final QRViewCreatedCallback onQRViewCreated;

final ShapeBorder overlay;
final EdgeInsetsGeometry overlayMargin;
final CameraFacing cameraFacing;

@override
State<StatefulWidget> createState() => _QRViewState();
}

class _QRViewState extends State<QRView> {

var _channel;

@override
Widget build(BuildContext context) {
return NotificationListener(
onNotification: onNotification,
child: SizeChangedLayoutNotifier(
child: (widget.overlay != null) ?
_getPlatformQrViewWithOverlay() :
_getPlatformQrView(),
),
);
}

bool onNotification(notification) {
Future.microtask(() => {
QRViewController.updateDimensions(
widget.key,
_channel,
scanArea: widget.overlay != null ?
(widget.overlay as QrScannerOverlayShape).cutOutSize : 0.0)
});
return false;
}

Widget _getPlatformQrViewWithOverlay() {
return Stack(
children: [
_getPlatformQrView(widget.key),
if (widget.overlay != null)
Container(
padding: widget.overlayMargin,
decoration: ShapeDecoration(
shape: widget.overlay,
),
)
else
Container(),
_getPlatformQrView(),
Container(
padding: widget.overlayMargin,
decoration: ShapeDecoration(
shape: widget.overlay,
),
)
],
);
}

Widget _getPlatformQrView(GlobalKey key) {
Widget _getPlatformQrView() {
Widget _platformQrView;
switch (defaultTargetPlatform) {
case TargetPlatform.android:
_platformQrView = AndroidView(
viewType: 'net.touchcapture.qr.flutterqr/qrview',
onPlatformViewCreated: _onPlatformViewCreated,
creationParams: _QrCameraSettings(cameraFacing: widget.cameraFacing).toMap(),
creationParamsCodec: StandardMessageCodec(),
);
break;
case TargetPlatform.iOS:
_platformQrView = UiKitView(
viewType: 'net.touchcapture.qr.flutterqr/qrview',
onPlatformViewCreated: _onPlatformViewCreated,
creationParams:
_CreationParams.fromWidget(MediaQuery.of(context).size.width, 400)
.toMap(),
creationParams: _QrCameraSettings(cameraFacing: widget.cameraFacing).toMap(),
creationParamsCodec: StandardMessageCodec(),
);
break;
@@ -155,101 +192,109 @@ class _QRViewState extends State<QRView> {
}

void _onPlatformViewCreated(int id) {
if (widget.onQRViewCreated == null) {
return;
}

// We pass the cutout size so that the scanner respects the scan area.
var cutOutSize = 0.0;
if (widget.overlay != null) {
cutOutSize = (widget.overlay as QrScannerOverlayShape).cutOutSize;
}

widget.onQRViewCreated(QRViewController._(id, widget.key, cutOutSize));
}
}
_channel = MethodChannel('net.touchcapture.qr.flutterqr/qrview_$id');

class _CreationParams {
_CreationParams({this.width, this.height});
// Start scan after creation of the view
final controller = QRViewController._(_channel, widget.key, cutOutSize)
.._startScan(widget.key, cutOutSize);

static _CreationParams fromWidget(double width, double height) {
return _CreationParams(
width: width,
height: height,
);
// Initialize the controller for controlling the QRView
if (widget.onQRViewCreated != null) {
widget.onQRViewCreated(controller);
}
}
}

class _QrCameraSettings {
_QrCameraSettings({
this.cameraFacing,
});

final double width;
final double height;
final CameraFacing cameraFacing;

Map<String, dynamic> toMap() {
return <String, dynamic>{
'width': width,
'height': height,
'cameraFacing': cameraFacing.index,
};
}

}

class QRViewController {
QRViewController._(int id, GlobalKey qrKey, double scanArea)
: _channel = MethodChannel('net.touchcapture.qr.flutterqr/qrview_$id') {
updateDimensions(qrKey, scanArea: scanArea);
_channel.setMethodCallHandler(
(call) async {
switch (call.method) {
case scanMethodCall:
if (call.arguments != null) {
final args = call.arguments as Map;
final code = args['code'] as String;
final rawType = args['type'] as String;
// Raw bytes are only supported by Android.
final rawBytes = args['rawBytes'] as List<int>;
final format = _formatNames[rawType];
if (format != null) {
final barcode = Barcode(code, format, rawBytes);
_scanUpdateController.sink.add(barcode);
} else {
throw Exception('Unexpected barcode type $rawType');
}
}
}
},
);
QRViewController._(MethodChannel channel, GlobalKey qrKey, double scanArea)
: _channel = channel {
_channel.setMethodCallHandler(_onMethodCall);
}

static const scanMethodCall = 'onRecognizeQR';

final MethodChannel _channel;

final StreamController<Barcode> _scanUpdateController =
StreamController<Barcode>();

Stream<Barcode> get scannedDataStream => _scanUpdateController.stream;

Future<void> _onMethodCall(MethodCall call) async {
switch (call.method) {
case 'onRecognizeQR':
if (call.arguments != null) {
final args = call.arguments as Map;
final code = args['code'] as String;
final rawType = args['type'] as String;
// Raw bytes are only supported by Android.
final rawBytes = args['rawBytes'] as List<int>;
final format = _formatNames[rawType];
if (format != null) {
final barcode = Barcode(code, format, rawBytes);
_scanUpdateController.sink.add(barcode);
} else {
throw Exception('Unexpected barcode type $rawType');
}
}
}
}

/// Starts the barcode scanner
Future<void> _startScan(GlobalKey key, double cutOutSize, ) async {
// We need to update the dimension before the scan is started.
QRViewController.updateDimensions(key, _channel, scanArea: cutOutSize);
return _channel.invokeMethod('startScan');
}

/// Flips the camera between available modes
void flipCamera() {
_channel.invokeMethod('flipCamera');
}

/// Toggles the flashlight between available modes
void toggleFlash() {
_channel.invokeMethod('toggleFlash');
}

/// Pauses barcode scanning
void pauseCamera() {
_channel.invokeMethod('pauseCamera');
}

/// Resumes barcode scanning
void resumeCamera() {
_channel.invokeMethod('resumeCamera');
}

/// Disposes the barcode stream.
void dispose() {
_scanUpdateController.close();
}

void updateDimensions(GlobalKey key, {double scanArea}) {
/// Updates the view dimensions for iOS.
static void updateDimensions(GlobalKey key, MethodChannel channel, {double scanArea}) {
if (defaultTargetPlatform == TargetPlatform.iOS) {
final RenderBox renderBox = key.currentContext.findRenderObject();
_channel.invokeMethod('setDimensions', {
channel.invokeMethod('setDimensions', {
'width': renderBox.size.width,
'height': renderBox.size.height,
'scanArea': scanArea ?? 0


Loading…
Cancel
Save