Julian Steenbakker

Merge branch 'master' into web

# Conflicts:
#	lib/src/mobile_scanner.dart
## 0.1.2
* MobileScannerArguments is now exported. [#7](https://github.com/juliansteenbakker/mobile_scanner/issues/7)
Bugfixes:
* Fixed application crashing when stop() or start() is called multiple times. [#5](https://github.com/juliansteenbakker/mobile_scanner/issues/5)
* Fixes controller not being disposed correctly. [#23](https://github.com/juliansteenbakker/mobile_scanner/issues/23)
* Catch error when no camera is found. [#19](https://github.com/juliansteenbakker/mobile_scanner/issues/19)
## 0.1.1
mobile_scanner is now compatible with sdk >= 2.12 and flutter >= 2.2.0
## 0.1.0
We now have MacOS support using Apple's Vision framework!
Keep in mind that for now, only the raw value is supported.
Keep in mind that for now, only the raw value of the barcode object is supported.
Bugfixes:
* Fixed a crash when dispose is called in a overridden method. [#5](https://github.com/juliansteenbakker/mobile_scanner/issues/5)
## 0.0.3
* Added some API docs and README
... ...
... ... @@ -3,13 +3,13 @@
[![pub package](https://img.shields.io/pub/v/mobile_scanner.svg)](https://pub.dev/packages/mobile_scanner)
[![mobile_scanner](https://github.com/juliansteenbakker/mobile_scanner/actions/workflows/flutter.yml/badge.svg)](https://github.com/juliansteenbakker/mobile_scanner/actions/workflows/flutter.yml)
A universal scanner for Flutter based on MLKit. Uses CameraX on Android and AVFoundation on iOS.
A universal barcode and QR code scanner for Flutter based on MLKit. Uses CameraX on Android, AVFoundation on iOS and Apple Vision & AVFoundation on macOS.
## Platform Support
| Android | iOS | MacOS | Web | Linux | Windows |
| :-----: | :-: | :---: | :-: | :---: | :-----: |
| ✔️ | ✔️ | | | | |
| ✔️ | ✔️ | ✔️ | | | |
CameraX for Android requires at least SDK 21.
... ...
include: package:flutter_lints/flutter.yaml
linter:
rules:
unawaited_futures: true
\ No newline at end of file
... ...
... ... @@ -38,8 +38,8 @@ class MobileScanner(private val activity: Activity, private val textureRegistry:
private var camera: Camera? = null
private var textureEntry: TextureRegistry.SurfaceTextureEntry? = null
@AnalyzeMode
private var analyzeMode: Int = AnalyzeMode.NONE
// @AnalyzeMode
// private var analyzeMode: Int = AnalyzeMode.NONE
@ExperimentalGetImage
override fun onMethodCall(@NonNull call: MethodCall, @NonNull result: MethodChannel.Result) {
... ... @@ -47,8 +47,8 @@ class MobileScanner(private val activity: Activity, private val textureRegistry:
"state" -> checkPermission(result)
"request" -> requestPermission(result)
"start" -> start(call, result)
"torch" -> switchTorch(call, result)
"analyze" -> switchAnalyzeMode(call, result)
"torch" -> toggleTorch(call, result)
// "analyze" -> switchAnalyzeMode(call, result)
"stop" -> stop(result)
else -> result.notImplemented()
}
... ... @@ -92,8 +92,8 @@ class MobileScanner(private val activity: Activity, private val textureRegistry:
@ExperimentalGetImage
val analyzer = ImageAnalysis.Analyzer { imageProxy -> // YUV_420_888 format
when (analyzeMode) {
AnalyzeMode.BARCODE -> {
// when (analyzeMode) {
// AnalyzeMode.BARCODE -> {
val mediaImage = imageProxy.image ?: return@Analyzer
val inputImage = InputImage.fromMediaImage(mediaImage, imageProxy.imageInfo.rotationDegrees)
... ... @@ -106,9 +106,9 @@ class MobileScanner(private val activity: Activity, private val textureRegistry:
}
.addOnFailureListener { e -> Log.e(TAG, e.message, e) }
.addOnCompleteListener { imageProxy.close() }
}
else -> imageProxy.close()
}
// }
// else -> imageProxy.close()
// }
}
... ... @@ -116,6 +116,10 @@ class MobileScanner(private val activity: Activity, private val textureRegistry:
@ExperimentalGetImage
private fun start(call: MethodCall, result: MethodChannel.Result) {
if (camera != null) {
result.error(TAG, "Called start() while already started!", null)
return
}
val facing: Int = call.argument<Int>("facing") ?: 0
val ratio: Int? = call.argument<Int>("ratio")
... ... @@ -186,24 +190,32 @@ class MobileScanner(private val activity: Activity, private val textureRegistry:
}, executor)
}
private fun switchTorch(call: MethodCall, result: MethodChannel.Result) {
val state = call.arguments == 1
camera!!.cameraControl.enableTorch(state)
result.success(null)
private fun toggleTorch(call: MethodCall, result: MethodChannel.Result) {
if (camera == null) {
result.error(TAG,"Called toggleTorch() while stopped!", null)
return
}
private fun switchAnalyzeMode(call: MethodCall, result: MethodChannel.Result) {
analyzeMode = call.arguments as Int
camera!!.cameraControl.enableTorch(call.arguments == 1)
result.success(null)
}
// private fun switchAnalyzeMode(call: MethodCall, result: MethodChannel.Result) {
// analyzeMode = call.arguments as Int
// result.success(null)
// }
private fun stop(result: MethodChannel.Result) {
if (camera == null) {
result.error(TAG,"Called stop() while already stopped!", null)
return
}
val owner = activity as LifecycleOwner
camera!!.cameraInfo.torchState.removeObservers(owner)
cameraProvider!!.unbindAll()
textureEntry!!.release()
analyzeMode = AnalyzeMode.NONE
// analyzeMode = AnalyzeMode.NONE
camera = null
textureEntry = null
cameraProvider = null
... ...
import 'package:flutter/material.dart';
import 'package:mobile_scanner/mobile_scanner.dart';
class BarcodeScannerWithController extends StatefulWidget {
const BarcodeScannerWithController({Key? key}) : super(key: key);
@override
_BarcodeScannerWithControllerState createState() =>
_BarcodeScannerWithControllerState();
}
class _BarcodeScannerWithControllerState
extends State<BarcodeScannerWithController>
with SingleTickerProviderStateMixin {
String? barcode;
MobileScannerController controller = MobileScannerController(
torchEnabled: true,
// facing: CameraFacing.front,
);
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
backgroundColor: Colors.black,
body: Builder(builder: (context) {
return Stack(
children: [
MobileScanner(
controller: controller,
fit: BoxFit.contain,
// controller: MobileScannerController(
// torchEnabled: true,
// facing: CameraFacing.front,
// ),
onDetect: (barcode, args) {
if (this.barcode != barcode.rawValue) {
setState(() {
this.barcode = barcode.rawValue;
});
}
}),
Align(
alignment: Alignment.bottomCenter,
child: Container(
alignment: Alignment.bottomCenter,
height: 100,
color: Colors.black.withOpacity(0.4),
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
IconButton(
color: Colors.white,
icon: ValueListenableBuilder(
valueListenable: controller.torchState,
builder: (context, state, child) {
switch (state as TorchState) {
case TorchState.off:
return const Icon(Icons.flash_off,
color: Colors.grey);
case TorchState.on:
return const Icon(Icons.flash_on,
color: Colors.yellow);
}
},
),
iconSize: 32.0,
onPressed: () => controller.toggleTorch(),
),
Center(
child: SizedBox(
width: MediaQuery.of(context).size.width - 120,
height: 50,
child: FittedBox(
child: Text(
barcode ?? 'Scan something!',
overflow: TextOverflow.fade,
style: Theme.of(context)
.textTheme
.headline4!
.copyWith(color: Colors.white),
),
),
),
),
IconButton(
color: Colors.white,
icon: ValueListenableBuilder(
valueListenable: controller.cameraFacingState,
builder: (context, state, child) {
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(),
),
],
),
),
),
],
);
}),
),
);
}
}
... ...
import 'package:flutter/material.dart';
import 'package:mobile_scanner/mobile_scanner.dart';
class BarcodeScannerWithoutController extends StatefulWidget {
const BarcodeScannerWithoutController({Key? key}) : super(key: key);
@override
_BarcodeScannerWithoutControllerState createState() =>
_BarcodeScannerWithoutControllerState();
}
class _BarcodeScannerWithoutControllerState
extends State<BarcodeScannerWithoutController>
with SingleTickerProviderStateMixin {
String? barcode;
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
backgroundColor: Colors.black,
body: Builder(builder: (context) {
return Stack(
children: [
MobileScanner(
fit: BoxFit.contain,
onDetect: (barcode, args) {
if (this.barcode != barcode.rawValue) {
setState(() {
this.barcode = barcode.rawValue;
});
}
}),
Align(
alignment: Alignment.bottomCenter,
child: Container(
alignment: Alignment.bottomCenter,
height: 100,
color: Colors.black.withOpacity(0.4),
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Center(
child: SizedBox(
width: MediaQuery.of(context).size.width - 120,
height: 50,
child: FittedBox(
child: Text(
barcode ?? 'Scan something!',
overflow: TextOverflow.fade,
style: Theme.of(context)
.textTheme
.headline4!
.copyWith(color: Colors.white),
),
),
),
),
],
),
),
),
],
);
}),
),
);
}
}
... ...
import 'package:flutter/material.dart';
import 'package:mobile_scanner/mobile_scanner.dart';
import 'package:mobile_scanner_example/barcode_scanner_controller.dart';
import 'package:mobile_scanner_example/barcode_scanner_without_controller.dart';
void main() {
runApp(const AnalyzeView());
}
class AnalyzeView extends StatefulWidget {
const AnalyzeView({Key? key}) : super(key: key);
void main() => runApp(const MaterialApp(home: MyHome()));
@override
_AnalyzeViewState createState() => _AnalyzeViewState();
}
class _AnalyzeViewState extends State<AnalyzeView>
with SingleTickerProviderStateMixin {
String? barcode;
MobileScannerController controller = MobileScannerController(
torchEnabled: true,
facing: CameraFacing.front,
);
class MyHome extends StatelessWidget {
const MyHome({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
backgroundColor: Colors.black,
body: Builder(builder: (context) {
return Stack(
children: [
MobileScanner(
controller: controller,
fit: BoxFit.contain,
// controller: MobileScannerController(
// torchEnabled: true,
// facing: CameraFacing.front,
// ),
onDetect: (barcode, args) {
if (this.barcode != barcode.rawValue) {
setState(() {
this.barcode = barcode.rawValue;
});
}
}),
Align(
alignment: Alignment.bottomCenter,
child: Container(
alignment: Alignment.bottomCenter,
height: 100,
color: Colors.black.withOpacity(0.4),
child: Row(
return Scaffold(
appBar: AppBar(title: const Text('Flutter Demo Home Page')),
body: SizedBox(
width: MediaQuery.of(context).size.width,
height: MediaQuery.of(context).size.height,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
IconButton(
color: Colors.white,
icon: ValueListenableBuilder(
valueListenable: controller.torchState,
builder: (context, state, child) {
switch (state as TorchState) {
case TorchState.off:
return const Icon(Icons.flash_off,
color: Colors.grey);
case TorchState.on:
return const Icon(Icons.flash_on,
color: Colors.yellow);
}
ElevatedButton(
onPressed: () {
Navigator.of(context).push(MaterialPageRoute(
builder: (context) => const BarcodeScannerWithController(),
));
},
child: const Text('MobileScanner with Controller'),
),
iconSize: 32.0,
onPressed: () => controller.toggleTorch(),
),
Center(
child: SizedBox(
width: MediaQuery.of(context).size.width - 120,
height: 50,
child: FittedBox(
child: Text(
barcode ?? 'Scan something!',
overflow: TextOverflow.fade,
style: Theme.of(context)
.textTheme
.headline4!
.copyWith(color: Colors.white),
),
),
),
),
IconButton(
color: Colors.white,
icon: ValueListenableBuilder(
valueListenable: controller.cameraFacingState,
builder: (context, state, child) {
switch (state as CameraFacing) {
case CameraFacing.front:
return const Icon(Icons.camera_front);
case CameraFacing.back:
return const Icon(Icons.camera_rear);
}
ElevatedButton(
onPressed: () {
Navigator.of(context).push(MaterialPageRoute(
builder: (context) => const BarcodeScannerWithoutController(),
));
},
),
iconSize: 32.0,
onPressed: () => controller.switchCamera(),
child: const Text('MobileScanner without Controller'),
),
],
),
),
),
// Container(
// alignment: Alignment.bottomCenter,
// margin: EdgeInsets.only(bottom: 80.0),
// child: IconButton(
// icon: ValueListenableBuilder(
// valueListenable: cameraController.torchState,
// builder: (context, state, child) {
// final color =
// state == TorchState.off ? Colors.grey : Colors.white;
// return Icon(Icons.bolt, color: color);
// },
// ),
// iconSize: 32.0,
// onPressed: () => cameraController.torch(),
// ),
// ),
],
);
}),
),
);
}
@override
void dispose() {
// cameraController.dispose();
super.dispose();
}
void display(Barcode barcode) {
Navigator.of(context).popAndPushNamed('display', arguments: barcode);
}
}
// import 'package:flutter/material.dart';
// import 'package:flutter/rendering.dart';
// import 'package:mobile_scanner/mobile_scanner.dart';
//
// void main() {
// debugPaintSizeEnabled = false;
// runApp(HomePage());
// }
//
// class HomePage extends StatefulWidget {
// @override
// HomeState createState() => HomeState();
// }
//
// class HomeState extends State<HomePage> {
// @override
// Widget build(BuildContext context) {
// return MaterialApp(home: MyApp());
// }
// }
//
// class MyApp extends StatefulWidget {
// @override
// _MyAppState createState() => _MyAppState();
// }
//
// class _MyAppState extends State<MyApp> {
// String? qr;
// bool camState = false;
//
// @override
// initState() {
// super.initState();
// }
//
// @override
// Widget build(BuildContext context) {
// return Scaffold(
// appBar: AppBar(
// title: Text('Plugin example app'),
// ),
// body: Center(
// child: Column(
// crossAxisAlignment: CrossAxisAlignment.center,
// mainAxisAlignment: MainAxisAlignment.center,
// children: <Widget>[
// Expanded(
// child: camState
// ? Center(
// child: SizedBox(
// width: 300.0,
// height: 600.0,
// child: MobileScanner(
// onError: (context, error) => Text(
// error.toString(),
// style: TextStyle(color: Colors.red),
// ),
// qrCodeCallback: (code) {
// setState(() {
// qr = code;
// });
// },
// child: Container(
// decoration: BoxDecoration(
// color: Colors.transparent,
// border: Border.all(
// color: Colors.orange,
// width: 10.0,
// style: BorderStyle.solid),
// ),
// ),
// ),
// ),
// )
// : Center(child: Text("Camera inactive"))),
// Text("QRCODE: $qr"),
// ],
// ),
// ),
// floatingActionButton: FloatingActionButton(
// child: Text(
// "press me",
// textAlign: TextAlign.center,
// ),
// onPressed: () {
// setState(() {
// camState = !camState;
// });
// }),
// );
// }
// }
... ...
platform :osx, '10.11'
platform :osx, '10.13'
# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
ENV['COCOAPODS_DISABLE_STATS'] = 'true'
... ...
... ... @@ -26,7 +26,7 @@
33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F22044A3C60003C045 /* Assets.xcassets */; };
33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F42044A3C60003C045 /* MainMenu.xib */; };
33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */; };
5225F51353DA345E2811B6A4 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 65E614A1DF8B88C7B0CE1B97 /* Pods_Runner.framework */; };
5B9BD2ADBC68B74D80B57DF1 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = EC099C2B6D6B30BFB3FA6DB8 /* Pods_Runner.framework */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
... ... @@ -67,12 +67,12 @@
33E51913231747F40026EE4D /* DebugProfile.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = DebugProfile.entitlements; sourceTree = "<group>"; };
33E51914231749380026EE4D /* Release.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = Release.entitlements; sourceTree = "<group>"; };
33E5194F232828860026EE4D /* AppInfo.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = AppInfo.xcconfig; sourceTree = "<group>"; };
65E614A1DF8B88C7B0CE1B97 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; };
3CEE8DB43A84811F33EB0202 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = "<group>"; };
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = "<group>"; };
9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = "<group>"; };
CB0901144E09E7D7CA20584F /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = "<group>"; };
D522F9F6F348C5944077606B /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = "<group>"; };
F63009B5E287A1C82F9D7D2F /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = "<group>"; };
A1CBC07680A8ED396DBB68C0 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = "<group>"; };
CAD760C57A57D903AB03B47A /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = "<group>"; };
EC099C2B6D6B30BFB3FA6DB8 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
... ... @@ -80,19 +80,27 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
5225F51353DA345E2811B6A4 /* Pods_Runner.framework in Frameworks */,
5B9BD2ADBC68B74D80B57DF1 /* Pods_Runner.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
18927D60C719EB75FC0A6633 /* Frameworks */ = {
isa = PBXGroup;
children = (
EC099C2B6D6B30BFB3FA6DB8 /* Pods_Runner.framework */,
);
name = Frameworks;
sourceTree = "<group>";
};
20F8C9AA20C2A495C125E194 /* Pods */ = {
isa = PBXGroup;
children = (
CB0901144E09E7D7CA20584F /* Pods-Runner.debug.xcconfig */,
D522F9F6F348C5944077606B /* Pods-Runner.release.xcconfig */,
F63009B5E287A1C82F9D7D2F /* Pods-Runner.profile.xcconfig */,
CAD760C57A57D903AB03B47A /* Pods-Runner.debug.xcconfig */,
A1CBC07680A8ED396DBB68C0 /* Pods-Runner.release.xcconfig */,
3CEE8DB43A84811F33EB0202 /* Pods-Runner.profile.xcconfig */,
);
path = Pods;
sourceTree = "<group>";
... ... @@ -115,7 +123,7 @@
33CEB47122A05771004F2AC0 /* Flutter */,
33CC10EE2044A3C60003C045 /* Products */,
20F8C9AA20C2A495C125E194 /* Pods */,
3539353E79638640B4999C09 /* Frameworks */,
18927D60C719EB75FC0A6633 /* Frameworks */,
);
sourceTree = "<group>";
};
... ... @@ -162,14 +170,6 @@
path = Runner;
sourceTree = "<group>";
};
3539353E79638640B4999C09 /* Frameworks */ = {
isa = PBXGroup;
children = (
65E614A1DF8B88C7B0CE1B97 /* Pods_Runner.framework */,
);
name = Frameworks;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
... ... @@ -177,13 +177,13 @@
isa = PBXNativeTarget;
buildConfigurationList = 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */;
buildPhases = (
696298230BDAD783AEC51C81 /* [CP] Check Pods Manifest.lock */,
20903D1E9D9F08576541FFD7 /* [CP] Check Pods Manifest.lock */,
33CC10E92044A3C60003C045 /* Sources */,
33CC10EA2044A3C60003C045 /* Frameworks */,
33CC10EB2044A3C60003C045 /* Resources */,
33CC110E2044A8840003C045 /* Bundle Framework */,
3399D490228B24CF009A79C7 /* ShellScript */,
8A90D2BC4083C5ACCEEBF32B /* [CP] Embed Pods Frameworks */,
DF45614760BB9B24F49B2055 /* [CP] Embed Pods Frameworks */,
);
buildRules = (
);
... ... @@ -253,7 +253,7 @@
/* End PBXResourcesBuildPhase section */
/* Begin PBXShellScriptBuildPhase section */
3399D490228B24CF009A79C7 /* ShellScript */ = {
20903D1E9D9F08576541FFD7 /* [CP] Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
... ... @@ -261,58 +261,58 @@
inputFileListPaths = (
);
inputPaths = (
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
"${PODS_ROOT}/Manifest.lock",
);
name = "[CP] Check Pods Manifest.lock";
outputFileListPaths = (
);
outputPaths = (
"$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "echo \"$PRODUCT_NAME.app\" > \"$PROJECT_DIR\"/Flutter/ephemeral/.app_filename && \"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh embed\n";
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
showEnvVarsInLog = 0;
};
33CC111E2044C6BF0003C045 /* ShellScript */ = {
3399D490228B24CF009A79C7 /* ShellScript */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
Flutter/ephemeral/FlutterInputs.xcfilelist,
);
inputPaths = (
Flutter/ephemeral/tripwire,
);
outputFileListPaths = (
Flutter/ephemeral/FlutterOutputs.xcfilelist,
);
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh && touch Flutter/ephemeral/tripwire";
shellScript = "echo \"$PRODUCT_NAME.app\" > \"$PROJECT_DIR\"/Flutter/ephemeral/.app_filename && \"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh embed\n";
};
696298230BDAD783AEC51C81 /* [CP] Check Pods Manifest.lock */ = {
33CC111E2044C6BF0003C045 /* ShellScript */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
Flutter/ephemeral/FlutterInputs.xcfilelist,
);
inputPaths = (
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
"${PODS_ROOT}/Manifest.lock",
Flutter/ephemeral/tripwire,
);
name = "[CP] Check Pods Manifest.lock";
outputFileListPaths = (
Flutter/ephemeral/FlutterOutputs.xcfilelist,
);
outputPaths = (
"$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
showEnvVarsInLog = 0;
shellScript = "\"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh && touch Flutter/ephemeral/tripwire";
};
8A90D2BC4083C5ACCEEBF32B /* [CP] Embed Pods Frameworks */ = {
DF45614760BB9B24F49B2055 /* [CP] Embed Pods Frameworks */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
... ...
... ... @@ -3,7 +3,7 @@ description: Demonstrates how to use the mobile_scanner plugin.
publish_to: 'none' # Remove this line if you wish to publish to pub.dev
environment:
sdk: ">=2.16.0 <3.0.0"
sdk: ">=2.12.0 <3.0.0"
dependencies:
flutter:
... ...
... ... @@ -23,7 +23,7 @@ public class SwiftMobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHan
var latestBuffer: CVImageBuffer!
var analyzeMode: Int = 0
// var analyzeMode: Int = 0
var analyzing: Bool = false
var position = AVCaptureDevice.Position.back
... ... @@ -53,9 +53,9 @@ public class SwiftMobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHan
case "start":
start(call, result)
case "torch":
switchTorch(call, result)
case "analyze":
switchAnalyzeMode(call, result)
toggleTorch(call, result)
// case "analyze":
// switchAnalyzeMode(call, result)
case "stop":
stop(result)
default:
... ... @@ -89,10 +89,10 @@ public class SwiftMobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHan
latestBuffer = CMSampleBufferGetImageBuffer(sampleBuffer)
registry.textureFrameAvailable(textureId)
switch analyzeMode {
case 1: // barcode
// switch analyzeMode {
// case 1: // barcode
if analyzing {
break
return
}
analyzing = true
let buffer = CMSampleBufferGetImageBuffer(sampleBuffer)
... ... @@ -112,9 +112,9 @@ public class SwiftMobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHan
}
analyzing = false
}
default: // none
break
}
// default: // none
// break
// }
}
func imageOrientation(
... ... @@ -154,6 +154,13 @@ public class SwiftMobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHan
}
func start(_ call: FlutterMethodCall, _ result: @escaping FlutterResult) {
if (device != nil) {
result(FlutterError(code: "MobileScanner",
message: "Called start() while already started!",
details: nil))
return
}
textureId = registry.register(self)
captureSession = AVCaptureSession()
... ... @@ -173,6 +180,13 @@ public class SwiftMobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHan
device = AVCaptureDevice.devices(for: .video).filter({$0.position == position}).first
}
if (device == nil) {
result(FlutterError(code: "MobileScanner",
message: "No camera found or failed to open camera!",
details: nil))
return
}
// Enable the torch if parameter is set and torch is available
if (device.hasTorch && device.isTorchAvailable) {
do {
... ... @@ -219,7 +233,13 @@ public class SwiftMobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHan
result(answer)
}
func switchTorch(_ call: FlutterMethodCall, _ result: @escaping FlutterResult) {
func toggleTorch(_ call: FlutterMethodCall, _ result: @escaping FlutterResult) {
if (device == nil) {
result(FlutterError(code: "MobileScanner",
message: "Called toggleTorch() while stopped!",
details: nil))
return
}
do {
try device.lockForConfiguration()
device.torchMode = call.arguments as! Int == 1 ? .on : .off
... ... @@ -230,12 +250,18 @@ public class SwiftMobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHan
}
}
func switchAnalyzeMode(_ call: FlutterMethodCall, _ result: @escaping FlutterResult) {
analyzeMode = call.arguments as! Int
result(nil)
}
// func switchAnalyzeMode(_ call: FlutterMethodCall, _ result: @escaping FlutterResult) {
// analyzeMode = call.arguments as! Int
// result(nil)
// }
func stop(_ result: FlutterResult) {
if (device == nil) {
result(FlutterError(code: "MobileScanner",
message: "Called stop() while already stopped!",
details: nil))
return
}
captureSession.stopRunning()
for input in captureSession.inputs {
captureSession.removeInput(input)
... ... @@ -246,7 +272,7 @@ public class SwiftMobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHan
device.removeObserver(self, forKeyPath: #keyPath(AVCaptureDevice.torchMode))
registry.unregisterTexture(textureId)
analyzeMode = 0
// analyzeMode = 0
latestBuffer = nil
captureSession = nil
device = nil
... ...
... ... @@ -2,4 +2,5 @@ library mobile_scanner;
export 'src/mobile_scanner.dart';
export 'src/mobile_scanner_controller.dart';
export 'src/mobile_scanner_arguments.dart';
export 'src/objects/barcode.dart';
... ...
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:mobile_scanner/mobile_scanner.dart';
... ... @@ -29,10 +28,12 @@ class MobileScanner extends StatefulWidget {
final BoxFit fit;
/// 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);
const MobileScanner({
Key? key,
this.onDetect,
this.controller,
this.fit = BoxFit.cover,
}) : super(key: key);
@override
State<MobileScanner> createState() => _MobileScannerState();
... ... @@ -40,30 +41,26 @@ class MobileScanner extends StatefulWidget {
class _MobileScannerState extends State<MobileScanner>
with WidgetsBindingObserver {
bool onScreen = true;
late MobileScannerController controller;
@override
void initState() {
super.initState();
if (widget.controller == null) {
controller = MobileScannerController();
} else {
controller = widget.controller!;
}
WidgetsBinding.instance?.addObserver(this);
controller = widget.controller ?? MobileScannerController();
}
@override
void didChangeAppLifecycleState(AppLifecycleState state) {
if (state == AppLifecycleState.resumed) {
setState(() => onScreen = true);
} else {
if (onScreen) {
switch (state) {
case AppLifecycleState.resumed:
controller.start();
break;
case AppLifecycleState.inactive:
case AppLifecycleState.paused:
case AppLifecycleState.detached:
controller.stop();
}
setState(() {
onScreen = false;
});
break;
}
}
... ... @@ -76,7 +73,6 @@ class _MobileScannerState extends State<MobileScanner>
);
} else {
return LayoutBuilder(builder: (context, BoxConstraints constraints) {
if (!onScreen) return const Text("Camera Paused.");
return ValueListenableBuilder(
valueListenable: controller.args,
builder: (context, value, child) {
... ... @@ -109,13 +105,32 @@ class _MobileScannerState extends State<MobileScanner>
);
}
});
}
});
}
}
@override
void didUpdateWidget(covariant MobileScanner oldWidget) {
super.didUpdateWidget(oldWidget);
if (oldWidget.controller == null) {
if (widget.controller != null) {
controller.dispose();
controller = widget.controller!;
}
} else {
if (widget.controller == null) {
controller = MobileScannerController();
} else if (oldWidget.controller != widget.controller) {
controller = widget.controller!;
}
}
}
@override
void dispose() {
controller.dispose();
WidgetsBinding.instance?.removeObserver(this);
super.dispose();
}
}
... ...
... ... @@ -4,7 +4,6 @@ import 'package:flutter/cupertino.dart';
import 'package:flutter/services.dart';
import 'package:mobile_scanner/mobile_scanner.dart';
import 'mobile_scanner_arguments.dart';
import 'objects/barcode_utility.dart';
/// The facing of a camera.
... ... @@ -27,7 +26,7 @@ enum TorchState {
on,
}
enum AnalyzeMode { none, barcode }
// enum AnalyzeMode { none, barcode }
class MobileScannerController {
MethodChannel methodChannel =
... ... @@ -62,8 +61,8 @@ class MobileScannerController {
// Sets analyze mode and barcode stream
barcodesController = StreamController.broadcast(
onListen: () => setAnalyzeMode(AnalyzeMode.barcode.index),
onCancel: () => setAnalyzeMode(AnalyzeMode.none.index),
// onListen: () => setAnalyzeMode(AnalyzeMode.barcode.index),
// onCancel: () => setAnalyzeMode(AnalyzeMode.none.index),
);
start();
... ... @@ -94,12 +93,13 @@ class MobileScannerController {
}
}
void setAnalyzeMode(int mode) {
if (hashCode != _controllerHashcode) {
return;
}
methodChannel.invokeMethod('analyze', mode);
}
// TODO: Add more analyzers like text analyzer
// void setAnalyzeMode(int mode) {
// if (hashCode != _controllerHashcode) {
// return;
// }
// methodChannel.invokeMethod('analyze', mode);
// }
// List<BarcodeFormats>? formats = _defaultBarcodeFormats,
/// Start barcode scanning. This will first check if the required permissions
... ... @@ -107,7 +107,7 @@ class MobileScannerController {
Future<void> start() async {
ensure('startAsync');
setAnalyzeMode(AnalyzeMode.barcode.index);
// setAnalyzeMode(AnalyzeMode.barcode.index);
// Check authorization status
MobileScannerState state =
MobileScannerState.values[await methodChannel.invokeMethod('state')];
... ... @@ -132,8 +132,15 @@ 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);
Map<String, dynamic>? startResult = {};
try {
startResult = await methodChannel.invokeMapMethod<String, dynamic>(
'start', arguments);
} on PlatformException catch (error) {
debugPrint('${error.code}: ${error.message}');
// setAnalyzeMode(AnalyzeMode.none.index);
return;
}
if (startResult == null) {
throw PlatformException(code: 'INITIALIZATION ERROR');
... ... @@ -146,17 +153,32 @@ class MobileScannerController {
hasTorch: hasTorch);
}
Future<void> stop() async => await methodChannel.invokeMethod('stop');
Future<void> stop() async {
try {
await methodChannel.invokeMethod('stop');
} on PlatformException catch (error) {
debugPrint('${error.code}: ${error.message}');
}
}
/// Switches the torch on or off.
///
/// Only works if torch is available.
void toggleTorch() {
Future<void> toggleTorch() async {
ensure('toggleTorch');
if (!hasTorch) return;
if (!hasTorch) {
debugPrint('Device has no torch/flash.');
return;
}
TorchState state =
torchState.value == TorchState.off ? TorchState.on : TorchState.off;
methodChannel.invokeMethod('torch', state.index);
try {
await methodChannel.invokeMethod('torch', state.index);
} on PlatformException catch (error) {
debugPrint('${error.code}: ${error.message}');
}
}
/// Switches the torch on or off.
... ... @@ -164,10 +186,16 @@ class MobileScannerController {
/// Only works if torch is available.
Future<void> switchCamera() async {
ensure('switchCamera');
await stop();
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;
start();
await start();
}
/// Disposes the controller and closes all listeners.
... ...
... ... @@ -25,7 +25,7 @@ class Barcode {
/// It's only available when the barcode is encoded in the UTF-8 format, and for non-UTF8 ones use [rawBytes] instead.
///
/// Returns null if the raw value can not be determined.
final String rawValue;
final String? rawValue;
/// Returns format type of the barcode value.
///
... ... @@ -63,7 +63,21 @@ class Barcode {
/// Gets parsed WiFi AP details.
final WiFi? wifi;
Barcode({this.corners, this.format = BarcodeFormat.ean13, this.rawBytes, this.type = BarcodeType.text, this.calendarEvent, this.contactInfo, this.driverLicense, this.email, this.geoPoint, this.phone, this.sms, this.url, this.wifi, required this.rawValue});
Barcode(
{this.corners,
this.format = BarcodeFormat.ean13,
this.rawBytes,
this.type = BarcodeType.text,
this.calendarEvent,
this.contactInfo,
this.driverLicense,
this.email,
this.geoPoint,
this.phone,
this.sms,
this.url,
this.wifi,
required this.rawValue});
/// Create a [Barcode] from native data.
Barcode.fromNative(Map<dynamic, dynamic> data)
... ... @@ -151,22 +165,22 @@ class ContactInfo {
/// Gets contact person's organization.
///
/// Returns null if not available.
final String organization;
final String? organization;
/// Gets contact person's phones.
///
/// Returns an empty list if nothing found.
final List<Phone> phones;
final List<Phone>? phones;
/// Gets contact person's title.
///
/// Returns null if not available.
final String title;
final String? title;
/// Gets contact person's urls.
///
/// Returns an empty list if nothing found.
final List<String> urls;
final List<String>? urls;
/// Create a [ContactInfo] from native data.
ContactInfo.fromNative(Map<dynamic, dynamic> data)
... ... @@ -188,7 +202,9 @@ class Address {
final List<String> addressLines;
/// Gets type of the address.
final AddressType type;
///
/// Returns null if not available.
final AddressType? type;
/// Create a [Address] from native data.
Address.fromNative(Map<dynamic, dynamic> data)
... ... @@ -201,37 +217,37 @@ class PersonName {
/// Gets first name.
///
/// Returns null if not available.
final String first;
final String? first;
/// Gets middle name.
///
/// Returns null if not available.
final String middle;
final String? middle;
/// Gets last name.
///
/// Returns null if not available.
final String last;
final String? last;
/// Gets prefix of the name.
///
/// Returns null if not available.
final String prefix;
final String? prefix;
/// Gets suffix of the person's name.
///
/// Returns null if not available.
final String suffix;
final String? suffix;
/// Gets the properly formatted name.
///
/// Returns null if not available.
final String formattedName;
final String? formattedName;
/// Designates a text string to be set as the kana name in the phonebook. Used for Japanese contacts.
///
/// Returns null if not available.
final String pronunciation;
final String? pronunciation;
/// Create a [PersonName] from native data.
PersonName.fromNative(Map<dynamic, dynamic> data)
... ... @@ -249,74 +265,74 @@ class DriverLicense {
/// Gets city of holder's address.
///
/// Returns null if not available.
final String addressCity;
final String? addressCity;
/// Gets state of holder's address.
///
/// Returns null if not available.
final String addressState;
final String? addressState;
/// Gets holder's street address.
///
/// Returns null if not available.
final String addressStreet;
final String? addressStreet;
/// Gets postal code of holder's address.
///
/// Returns null if not available.
final String addressZip;
final String? addressZip;
/// Gets birth date of the holder.
///
/// Returns null if not available.
final String birthDate;
final String? birthDate;
/// Gets "DL" for driver licenses, "ID" for ID cards.
///
/// Returns null if not available.
final String documentType;
final String? documentType;
/// Gets expiry date of the license.
///
/// Returns null if not available.
final String expiryDate;
final String? expiryDate;
/// Gets holder's first name.
///
/// Returns null if not available.
final String firstName;
final String? firstName;
/// Gets holder's gender. 1 - male, 2 - female.
///
/// Returns null if not available.
final String gender;
final String? gender;
/// Gets issue date of the license.
///
/// The date format depends on the issuing country. MMDDYYYY for the US, YYYYMMDD for Canada.
///
/// Returns null if not available.
final String issueDate;
final String? issueDate;
/// Gets the three-letter country code in which DL/ID was issued.
///
/// Returns null if not available.
final String issuingCountry;
final String? issuingCountry;
/// Gets holder's last name.
///
/// Returns null if not available.
final String lastName;
final String? lastName;
/// Gets driver license ID number.
///
/// Returns null if not available.
final String licenseNumber;
final String? licenseNumber;
/// Gets holder's middle name.
///
/// Returns null if not available.
final String middleName;
final String? middleName;
/// Create a [DriverLicense] from native data.
DriverLicense.fromNative(Map<dynamic, dynamic> data)
... ... @@ -341,22 +357,23 @@ class Email {
/// Gets email's address.
///
/// Returns null if not available.
final String address;
final String? address;
/// Gets email's body.
///
/// Returns null if not available.
final String body;
final String? body;
/// Gets email's subject.
///
/// Returns null if not available.
final String subject;
final String? subject;
/// Gets type of the email.
///
/// See also [EmailType].
final EmailType type;
/// Returns null if not available.
final EmailType? type;
/// Create a [Email] from native data.
Email.fromNative(Map<dynamic, dynamic> data)
... ... @@ -369,10 +386,10 @@ class Email {
/// GPS coordinates from a 'GEO:' or similar QRCode type.
class GeoPoint {
/// Gets the latitude.
final double latitude;
final double? latitude;
/// Gets the longitude.
final double longitude;
final double? longitude;
/// Create a [GeoPoint] from native data.
GeoPoint.fromNative(Map<dynamic, dynamic> data)
... ... @@ -385,12 +402,13 @@ class Phone {
/// Gets phone number.
///
/// Returns null if not available.
final String number;
final String? number;
/// Gets type of the phone number.
///
/// See also [PhoneType].
final PhoneType type;
/// Returns null if not available.
final PhoneType? type;
/// Create a [Phone] from native data.
Phone.fromNative(Map<dynamic, dynamic> data)
... ... @@ -403,12 +421,12 @@ class SMS {
/// Gets the message content of the sms.
///
/// Returns null if not available.
final String message;
final String? message;
/// Gets the phone number of the sms.
///
/// Returns null if not available.
final String phoneNumber;
final String? phoneNumber;
/// Create a [SMS] from native data.
SMS.fromNative(Map<dynamic, dynamic> data)
... ... @@ -421,12 +439,12 @@ class UrlBookmark {
/// Gets the title of the bookmark.
///
/// Returns null if not available.
final String title;
final String? title;
/// Gets the url of the bookmark.
///
/// Returns null if not available.
final String url;
final String? url;
/// Create a [UrlBookmark] from native data.
UrlBookmark.fromNative(Map<dynamic, dynamic> data)
... ... @@ -444,12 +462,12 @@ class WiFi {
/// Gets the ssid of the WIFI.
///
/// Returns null if not available.
final String ssid;
final String? ssid;
/// Gets the password of the WIFI.
///
/// Returns null if not available.
final String password;
final String? password;
/// Create a [WiFi] from native data.
WiFi.fromNative(Map<dynamic, dynamic> data)
... ...
... ... @@ -22,7 +22,7 @@ public class MobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHandler,
var latestBuffer: CVImageBuffer!
var analyzeMode: Int = 0
// var analyzeMode: Int = 0
var analyzing: Bool = false
var position = AVCaptureDevice.Position.back
... ... @@ -52,9 +52,9 @@ public class MobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHandler,
case "start":
start(call, result)
case "torch":
switchTorch(call, result)
case "analyze":
switchAnalyzeMode(call, result)
toggleTorch(call, result)
// case "analyze":
// switchAnalyzeMode(call, result)
case "stop":
stop(result)
default:
... ... @@ -92,14 +92,14 @@ public class MobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHandler,
latestBuffer = CMSampleBufferGetImageBuffer(sampleBuffer)
registry.textureFrameAvailable(textureId)
switch analyzeMode {
case 1: // barcode
// switch analyzeMode {
// case 1: // barcode
// Limit the analyzer because the texture output will freeze otherwise
if i / 10 == 1 {
i = 0
} else {
break
return
}
let imageRequestHandler = VNImageRequestHandler(
cvPixelBuffer: latestBuffer,
... ... @@ -129,9 +129,9 @@ public class MobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHandler,
print(error)
}
default: // none
break
}
// default: // none
// break
// }
}
func checkPermission(_ call: FlutterMethodCall, _ result: @escaping FlutterResult) {
... ... @@ -159,6 +159,13 @@ public class MobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHandler,
}
func start(_ call: FlutterMethodCall, _ result: @escaping FlutterResult) {
if (device != nil) {
result(FlutterError(code: "MobileScanner",
message: "Called start() while already started!",
details: nil))
return
}
textureId = registry.register(self)
captureSession = AVCaptureSession()
... ... @@ -178,6 +185,13 @@ public class MobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHandler,
device = AVCaptureDevice.devices(for: .video).filter({$0.position == position}).first
}
if (device == nil) {
result(FlutterError(code: "MobileScanner",
message: "No camera found or failed to open camera!",
details: nil))
return
}
// Enable the torch if parameter is set and torch is available
if (device.hasTorch) {
do {
... ... @@ -222,7 +236,13 @@ public class MobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHandler,
result(answer)
}
func switchTorch(_ call: FlutterMethodCall, _ result: @escaping FlutterResult) {
func toggleTorch(_ call: FlutterMethodCall, _ result: @escaping FlutterResult) {
if (device == nil) {
result(FlutterError(code: "MobileScanner",
message: "Called toggleTorch() while stopped!",
details: nil))
return
}
do {
try device.lockForConfiguration()
device.torchMode = call.arguments as! Int == 1 ? .on : .off
... ... @@ -233,12 +253,18 @@ public class MobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHandler,
}
}
func switchAnalyzeMode(_ call: FlutterMethodCall, _ result: @escaping FlutterResult) {
analyzeMode = call.arguments as! Int
result(nil)
}
// func switchAnalyzeMode(_ call: FlutterMethodCall, _ result: @escaping FlutterResult) {
// analyzeMode = call.arguments as! Int
// result(nil)
// }
func stop(_ result: FlutterResult) {
if (device == nil) {
result(FlutterError(code: "MobileScanner",
message: "Called stop() while already stopped!",
details: nil))
return
}
captureSession.stopRunning()
for input in captureSession.inputs {
captureSession.removeInput(input)
... ... @@ -249,7 +275,7 @@ public class MobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHandler,
device.removeObserver(self, forKeyPath: #keyPath(AVCaptureDevice.torchMode))
registry.unregisterTexture(textureId)
analyzeMode = 0
// analyzeMode = 0
latestBuffer = nil
captureSession = nil
device = nil
... ...
name: mobile_scanner
description: A universal scanner for Flutter based on MLKit. Uses CameraX on Android and AVFoundation on iOS.
version: 0.1.0
description: A universal barcode and QR code scanner for Flutter based on MLKit. Uses CameraX on Android, AVFoundation on iOS and Apple Vision & AVFoundation on macOS.
version: 0.1.2
repository: https://github.com/juliansteenbakker/mobile_scanner
environment:
sdk: ">=2.16.0 <3.0.0"
flutter: ">=2.5.0"
sdk: ">=2.12.0 <3.0.0"
flutter: ">=2.2.0"
dependencies:
js: ^0.6.4
... ...