Julian Steenbakker

Merge branch 'master' into dependabot/github_actions/subosito/flutter-action-2.3.0

## 0.0.1
Initial release!
Things working on Android:
* Scanning barcodes using the latest version of MLKit and CameraX!
* Switching camera's
* Toggling of the torch (flash)
* TODO: Describe initial release.
Things working on iOS:
* Scanning barcodes using the latest version of MLKit and AVFoundation!
... ...
TODO: Add your license here.
BSD 3-Clause License
Copyright (c) 2022, Julian Steenbakker
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
... ...
... ... @@ -6,7 +6,7 @@ buildscript {
}
dependencies {
classpath 'com.android.tools.build:gradle:7.1.0'
classpath 'com.android.tools.build:gradle:7.1.1'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}
... ...
#Tue Feb 08 10:35:11 CET 2022
#Tue Feb 15 22:11:04 CET 2022
distributionBase=GRADLE_USER_HOME
distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-bin.zip
distributionPath=wrapper/dists
zipStorePath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
... ...
import 'dart:ui';
import 'package:flutter/material.dart';
import 'package:mobile_scanner/mobile_scanner.dart';
... ... @@ -18,8 +16,10 @@ class _AnalyzeViewState extends State<AnalyzeView>
with SingleTickerProviderStateMixin {
String? barcode;
MobileScannerController controller = MobileScannerController(torchEnabled: true,
facing: CameraFacing.front,);
MobileScannerController controller = MobileScannerController(
torchEnabled: true,
facing: CameraFacing.front,
);
@override
Widget build(BuildContext context) {
... ... @@ -30,7 +30,7 @@ class _AnalyzeViewState extends State<AnalyzeView>
return Stack(
children: [
MobileScanner(
controller: controller,
controller: controller,
fit: BoxFit.contain,
// controller: MobileScannerController(
// torchEnabled: true,
... ... @@ -55,20 +55,22 @@ class _AnalyzeViewState extends State<AnalyzeView>
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(),
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,
... ...
... ... @@ -20,7 +20,7 @@ class _AnalyzeViewState extends State<AnalyzeView>
// CameraController cameraController = CameraController(context, width: 320, height: 150);
String? barcode = null;
String? barcode;
@override
Widget build(BuildContext context) {
... ... @@ -30,40 +30,41 @@ class _AnalyzeViewState extends State<AnalyzeView>
return Stack(
children: [
MobileScanner(
// fitScreen: false,
// 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;
});
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));
}
}
// Default 640 x480
}),
this.points = points;
});
}
}
// Default 640 x480
}),
CustomPaint(
painter: OpenPainter(points),
),
... ... @@ -108,7 +109,7 @@ class OpenPainter extends CustomPainter {
@override
void paint(Canvas canvas, Size size) {
var paint1 = Paint()
..color = Color(0xff63aa65)
..color = const Color(0xff63aa65)
..strokeWidth = 10;
//draw points on canvas
canvas.drawPoints(PointMode.points, points, paint1);
... ...
... ... @@ -5,11 +5,8 @@
// gestures. You can also use WidgetTester to find child widgets in the widget
// tree, read text, and verify that the values of widget properties are correct.
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mobile_scanner_example/main.dart';
void main() {
testWidgets('Verify Platform version', (WidgetTester tester) async {
// Build our app and trigger a frame.
... ...
//
// SwiftMobileScanner.swift
// mobile_scanner
//
// Created by Julian Steenbakker on 15/02/2022.
//
import Foundation
... ...
... ... @@ -5,61 +5,77 @@ import MLKitBarcodeScanning
public class SwiftMobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHandler, FlutterTexture, AVCaptureVideoDataOutputSampleBufferDelegate {
public static func register(with registrar: FlutterPluginRegistrar) {
let instance = SwiftMobileScannerPlugin(registrar.textures())
let method = FlutterMethodChannel(name: "dev.steenbakker.mobile_scanner/scanner/method", binaryMessenger: registrar.messenger())
registrar.addMethodCallDelegate(instance, channel: method)
let event = FlutterEventChannel(name: "dev.steenbakker.mobile_scanner/scanner/event", binaryMessenger: registrar.messenger())
event.setStreamHandler(instance)
}
let registry: FlutterTextureRegistry
// Sink for publishing event changes
var sink: FlutterEventSink!
// Texture id of the camera preview
var textureId: Int64!
// Capture session of the camera
var captureSession: AVCaptureSession!
// The selected camera
var device: AVCaptureDevice!
// Image to be sent to the texture
var latestBuffer: CVImageBuffer!
var analyzeMode: Int
var analyzing: Bool
var analyzeMode: Int = 0
var analyzing: Bool = false
var position = AVCaptureDevice.Position.back
public static func register(with registrar: FlutterPluginRegistrar) {
let instance = SwiftMobileScannerPlugin(registrar.textures())
let method = FlutterMethodChannel(name:
"dev.steenbakker.mobile_scanner/scanner/method", binaryMessenger: registrar.messenger())
let event = FlutterEventChannel(name:
"dev.steenbakker.mobile_scanner/scanner/event", binaryMessenger: registrar.messenger())
registrar.addMethodCallDelegate(instance, channel: method)
event.setStreamHandler(instance)
}
init(_ registry: FlutterTextureRegistry) {
self.registry = registry
analyzeMode = 0
analyzing = false
super.init()
}
public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
switch call.method {
case "state":
stateNative(call, result)
checkPermission(call, result)
case "request":
requestNative(call, result)
requestPermission(call, result)
case "start":
startNative(call, result)
start(call, result)
case "torch":
torchNative(call, result)
switchTorch(call, result)
case "analyze":
analyzeNative(call, result)
switchAnalyzeMode(call, result)
case "stop":
stopNative(result)
stop(result)
default:
result(FlutterMethodNotImplemented)
}
}
// FlutterStreamHandler
public func onListen(withArguments arguments: Any?, eventSink events: @escaping FlutterEventSink) -> FlutterError? {
sink = events
return nil
}
// FlutterStreamHandler
public func onCancel(withArguments arguments: Any?) -> FlutterError? {
sink = nil
return nil
}
// FlutterTexture
public func copyPixelBuffer() -> Unmanaged<CVPixelBuffer>? {
if latestBuffer == nil {
return nil
... ... @@ -67,11 +83,12 @@ public class SwiftMobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHan
return Unmanaged<CVPixelBuffer>.passRetained(latestBuffer)
}
// Gets called when a new image is added to the buffer
public func captureOutput(_ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) {
latestBuffer = CMSampleBufferGetImageBuffer(sampleBuffer)
registry.textureFrameAvailable(textureId)
switch analyzeMode {
case 1: // barcode
if analyzing {
... ... @@ -84,7 +101,7 @@ public class SwiftMobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHan
deviceOrientation: UIDevice.current.orientation,
defaultOrientation: .portrait
)
let scanner = BarcodeScanner.barcodeScanner()
scanner.process(image) { [self] barcodes, error in
if error == nil && barcodes != nil {
... ... @@ -120,7 +137,7 @@ public class SwiftMobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHan
}
}
func stateNative(_ call: FlutterMethodCall, _ result: @escaping FlutterResult) {
func checkPermission(_ call: FlutterMethodCall, _ result: @escaping FlutterResult) {
let status = AVCaptureDevice.authorizationStatus(for: .video)
switch status {
case .notDetermined:
... ... @@ -132,34 +149,45 @@ public class SwiftMobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHan
}
}
func requestNative(_ call: FlutterMethodCall, _ result: @escaping FlutterResult) {
func requestPermission(_ call: FlutterMethodCall, _ result: @escaping FlutterResult) {
AVCaptureDevice.requestAccess(for: .video, completionHandler: { result($0) })
}
var position = AVCaptureDevice.Position.back
func startNative(_ call: FlutterMethodCall, _ result: @escaping FlutterResult) {
func start(_ call: FlutterMethodCall, _ result: @escaping FlutterResult) {
textureId = registry.register(self)
captureSession = AVCaptureSession()
let argReader = MapArgumentReader(call.arguments as? [String: Any])
guard let targetWidth = argReader.int(key: "targetWidth"),
let targetHeight = argReader.int(key: "targetHeight"),
let facing = argReader.int(key: "facing") else {
result(FlutterError(code: "INVALID_ARGUMENT", message: "Missing a required argument", details: "Expecting targetWidth, targetHeight, formats, and optionally heartbeatTimeout"))
return
}
// let ratio: Int = argReader.int(key: "ratio")
let torch: Bool = argReader.bool(key: "torch") ?? false
let facing: Int = argReader.int(key: "facing") ?? 1
// Set the camera to use
position = facing == 0 ? AVCaptureDevice.Position.front : .back
// Open the camera device
if #available(iOS 10.0, *) {
device = AVCaptureDevice.DiscoverySession(deviceTypes: [.builtInWideAngleCamera], mediaType: .video, position: position).devices.first
} else {
device = AVCaptureDevice.devices(for: .video).filter({$0.position == position}).first
}
// Enable the torch if parameter is set and torch is available
if (device.hasTorch && device.isTorchAvailable) {
do {
try device.lockForConfiguration()
device.torchMode = torch ? .on : .off
device.unlockForConfiguration()
} catch {
error.throwNative(result)
}
}
device.addObserver(self, forKeyPath: #keyPath(AVCaptureDevice.torchMode), options: .new, context: nil)
captureSession.beginConfiguration()
// Add device input.
// Add device input
do {
let input = try AVCaptureDeviceInput(device: device)
captureSession.addInput(input)
... ... @@ -191,7 +219,7 @@ public class SwiftMobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHan
result(answer)
}
func torchNative(_ call: FlutterMethodCall, _ result: @escaping FlutterResult) {
func switchTorch(_ call: FlutterMethodCall, _ result: @escaping FlutterResult) {
do {
try device.lockForConfiguration()
device.torchMode = call.arguments as! Int == 1 ? .on : .off
... ... @@ -202,12 +230,12 @@ public class SwiftMobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHan
}
}
func analyzeNative(_ call: FlutterMethodCall, _ result: @escaping FlutterResult) {
func switchAnalyzeMode(_ call: FlutterMethodCall, _ result: @escaping FlutterResult) {
analyzeMode = call.arguments as! Int
result(nil)
}
func stopNative(_ result: FlutterResult) {
func stop(_ result: FlutterResult) {
captureSession.stopRunning()
for input in captureSession.inputs {
captureSession.removeInput(input)
... ... @@ -227,6 +255,7 @@ public class SwiftMobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHan
result(nil)
}
// Observer for torch state
public override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
switch keyPath {
case "torchMode":
... ... @@ -255,6 +284,10 @@ class MapArgumentReader {
func int(key: String) -> Int? {
return (args?[key] as? NSNumber)?.intValue
}
func bool(key: String) -> Bool? {
return (args?[key] as? NSNumber)?.boolValue
}
func stringArray(key: String) -> [String]? {
return args?[key] as? [String]
... ...
... ... @@ -2,4 +2,4 @@ library mobile_scanner;
export 'src/mobile_scanner.dart';
export 'src/mobile_scanner_controller.dart';
export 'src/objects/barcode.dart';
\ No newline at end of file
export 'src/objects/barcode.dart';
... ...
... ... @@ -3,10 +3,7 @@ import 'package:mobile_scanner/mobile_scanner.dart';
import 'mobile_scanner_arguments.dart';
enum Ratio {
ratio_4_3,
ratio_16_9
}
enum Ratio { ratio_4_3, ratio_16_9 }
/// A widget showing a live camera preview.
class MobileScanner extends StatefulWidget {
... ... @@ -18,7 +15,8 @@ class MobileScanner extends StatefulWidget {
/// Create a [MobileScanner] with a [controller], the [controller] must has been initialized.
const MobileScanner(
{Key? key, this.onDetect, this.controller, this.fit = BoxFit.cover})
: assert((controller != null )), super(key: key);
: assert((controller != null)),
super(key: key);
@override
State<MobileScanner> createState() => _MobileScannerState();
... ... @@ -91,4 +89,4 @@ class _MobileScannerState extends State<MobileScanner>
controller.dispose();
super.dispose();
}
}
\ No newline at end of file
}
... ...
... ... @@ -11,5 +11,6 @@ class MobileScannerArguments {
final bool hasTorch;
/// Create a [MobileScannerArguments].
MobileScannerArguments({required this.textureId,required this.size, required this.hasTorch});
MobileScannerArguments(
{required this.textureId, required this.size, required this.hasTorch});
}
... ...
... ... @@ -38,7 +38,6 @@ class MobileScannerController {
int? _controllerHashcode;
StreamSubscription? events;
final ValueNotifier<MobileScannerArguments?> args = ValueNotifier(null);
final ValueNotifier<TorchState> torchState = ValueNotifier(TorchState.off);
late final ValueNotifier<CameraFacing> cameraFacingState;
... ... @@ -107,7 +106,8 @@ class MobileScannerController {
setAnalyzeMode(AnalyzeMode.barcode.index);
// Check authorization status
MobileScannerState state = MobileScannerState.values[await methodChannel.invokeMethod('state')];
MobileScannerState state =
MobileScannerState.values[await methodChannel.invokeMethod('state')];
switch (state) {
case MobileScannerState.undetermined:
final bool result = await methodChannel.invokeMethod('request');
... ... @@ -129,13 +129,18 @@ class MobileScannerController {
if (torchEnabled != null) arguments['torch'] = torchEnabled;
// Start the camera with arguments
final Map<String, dynamic>? startResult = await methodChannel.invokeMapMethod<String, dynamic>(
'start', arguments);
final Map<String, dynamic>? startResult = await methodChannel
.invokeMapMethod<String, dynamic>('start', arguments);
if (startResult == null) throw PlatformException(code: 'INITIALIZATION ERROR');
if (startResult == null) {
throw PlatformException(code: 'INITIALIZATION ERROR');
}
hasTorch = startResult['torchable'];
args.value = MobileScannerArguments(textureId: startResult['textureId'], size: toSize(startResult['size']), hasTorch: hasTorch);
args.value = MobileScannerArguments(
textureId: startResult['textureId'],
size: toSize(startResult['size']),
hasTorch: hasTorch);
}
Future<void> stop() async => await methodChannel.invokeMethod('stop');
... ... @@ -157,7 +162,8 @@ class MobileScannerController {
Future<void> switchCamera() async {
ensure('switchCamera');
await stop();
facing = facing == CameraFacing.back ? CameraFacing.front : CameraFacing.back;
facing =
facing == CameraFacing.back ? CameraFacing.front : CameraFacing.back;
start();
}
... ...
... ... @@ -490,7 +490,7 @@ enum BarcodeFormat {
/// Barcode format constant for Data Matrix.
///
/// Constant Value: 16
data_matrix,
dataMatrix,
/// Barcode format constant for EAN-13.
///
... ... @@ -510,17 +510,17 @@ enum BarcodeFormat {
/// Barcode format constant for QR Code.
///
/// Constant Value: 256
qr_code,
qrCode,
/// Barcode format constant for UPC-A.
///
/// Constant Value: 512
upc_a,
upcA,
/// Barcode format constant for UPC-E.
///
/// Constant Value: 1024
upc_e,
upcE,
/// Barcode format constant for PDF-417.
///
... ...
... ... @@ -29,7 +29,7 @@ BarcodeFormat toFormat(int value) {
case 8:
return BarcodeFormat.codebar;
case 16:
return BarcodeFormat.data_matrix;
return BarcodeFormat.dataMatrix;
case 32:
return BarcodeFormat.ean13;
case 64:
... ... @@ -37,11 +37,11 @@ BarcodeFormat toFormat(int value) {
case 128:
return BarcodeFormat.itf;
case 256:
return BarcodeFormat.qr_code;
return BarcodeFormat.qrCode;
case 512:
return BarcodeFormat.upc_a;
return BarcodeFormat.upcA;
case 1024:
return BarcodeFormat.upc_e;
return BarcodeFormat.upcE;
case 2048:
return BarcodeFormat.pdf417;
case 4096:
... ...
... ... @@ -23,6 +23,4 @@ flutter:
package: dev.steenbakker.mobile_scanner
pluginClass: MobileScannerPlugin
ios:
pluginClass: MobileScannerPlugin
macos:
pluginClass: MobileScannerPlugin
pluginClass: MobileScannerPlugin
\ No newline at end of file
... ...
import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mobile_scanner/src/mobile_scanner.dart';
void main() {
const MethodChannel channel = MethodChannel('mobile_scanner');
... ... @@ -16,5 +15,4 @@ void main() {
tearDown(() {
channel.setMockMethodCallHandler(null);
});
}
... ...