Julian Steenbakker

Merge branch 'master' into image-picker

# Conflicts:
#	example/lib/main.dart
## 0.1.3
* Fixed crash after asking permission. [#29](https://github.com/juliansteenbakker/mobile_scanner/issues/29)
* Upgraded cameraX from 1.1.0-beta01 to 1.1.0-beta02
## 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
... ...
include: package:flutter_lints/flutter.yaml
\ No newline at end of file
include: package:flutter_lints/flutter.yaml
linter:
rules:
unawaited_futures: true
\ No newline at end of file
... ...
... ... @@ -9,7 +9,7 @@ buildscript {
}
dependencies {
classpath 'com.android.tools.build:gradle:7.1.1'
classpath 'com.android.tools.build:gradle:7.1.2'
}
}
... ... @@ -47,8 +47,8 @@ android {
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation 'com.google.mlkit:barcode-scanning:17.0.2'
implementation "androidx.camera:camera-camera2:1.1.0-beta01"
implementation 'androidx.camera:camera-lifecycle:1.1.0-beta01'
implementation "androidx.camera:camera-camera2:1.1.0-beta02"
implementation 'androidx.camera:camera-lifecycle:1.1.0-beta02'
// // The following line is optional, as the core library is included indirectly by camera-camera2
// implementation "androidx.camera:camera-core:1.1.0-alpha11"
... ...
... ... @@ -41,8 +41,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) {
... ... @@ -50,8 +50,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)
"analyzeImage" -> analyzeImage(call, result)
else -> result.notImplemented()
... ... @@ -96,8 +96,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)
... ... @@ -110,9 +110,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()
// }
}
... ... @@ -120,6 +120,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")
... ... @@ -136,6 +140,7 @@ class MobileScanner(private val activity: Activity, private val textureRegistry:
future.addListener({
cameraProvider = future.get()
cameraProvider!!.unbindAll()
textureEntry = textureRegistry.createSurfaceTexture()
// Preview
... ... @@ -190,16 +195,19 @@ 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)
private fun toggleTorch(call: MethodCall, result: MethodChannel.Result) {
if (camera == null) {
result.error(TAG,"Called toggleTorch() while stopped!", null)
return
}
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 switchAnalyzeMode(call: MethodCall, result: MethodChannel.Result) {
// analyzeMode = call.arguments as Int
// result.success(null)
// }
private fun analyzeImage(call: MethodCall, result: MethodChannel.Result) {
val uri = Uri.fromFile( File(call.arguments.toString()))
... ... @@ -219,12 +227,17 @@ class MobileScanner(private val activity: Activity, private val textureRegistry:
}
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
... ...
... ... @@ -6,7 +6,7 @@ buildscript {
}
dependencies {
classpath 'com.android.tools.build:gradle:7.1.1'
classpath 'com.android.tools.build:gradle:7.1.2'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}
... ...
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,
);
bool isStarted = true;
@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(),
),
IconButton(
color: Colors.white,
icon: isStarted
? const Icon(Icons.stop)
: const Icon(Icons.play_arrow),
iconSize: 32.0,
onPressed: () => setState(() {
isStarted
? controller.stop()
: controller.start();
isStarted = !isStarted;
})),
Center(
child: SizedBox(
width: MediaQuery.of(context).size.width - 160,
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:image_picker/image_picker.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());
}
void main() => runApp(const MaterialApp(home: MyHome()));
class AnalyzeView extends StatefulWidget {
const AnalyzeView({Key? key}) : super(key: key);
@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(
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 - 180,
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(),
),
IconButton(
color: Colors.white,
icon: Icon(Icons.browse_gallery),
iconSize: 32.0,
onPressed: () async {
final ImagePicker _picker = ImagePicker();
// Pick an image
final XFile? image = await _picker.pickImage(source: ImageSource.gallery);
if (image != null) {
controller.analyzeImage(image.path);
}
},
),
],
),
),
),
// 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(),
// ),
// ),
],
);
}),
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,
children: [
ElevatedButton(
onPressed: () {
Navigator.of(context).push(MaterialPageRoute(
builder: (context) => const BarcodeScannerWithController(),
));
},
child: const Text('MobileScanner with Controller'),
),
ElevatedButton(
onPressed: () {
Navigator.of(context).push(MaterialPageRoute(
builder: (context) => const BarcodeScannerWithoutController(),
));
},
child: const Text('MobileScanner without Controller'),
),
],
),
),
);
}
@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 = (
... ...
... ... @@ -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/material.dart';
import 'package:mobile_scanner/mobile_scanner.dart';
import 'mobile_scanner_arguments.dart';
enum Ratio { ratio_4_3, ratio_16_9 }
/// A widget showing a live camera preview.
... ... @@ -31,8 +29,7 @@ class MobileScanner extends StatefulWidget {
this.onDetect,
this.controller,
this.fit = BoxFit.cover,
}) : assert((controller != null)),
super(key: key);
}) : super(key: key);
@override
State<MobileScanner> createState() => _MobileScannerState();
... ... @@ -40,33 +37,32 @@ class MobileScanner extends StatefulWidget {
class _MobileScannerState extends State<MobileScanner>
with WidgetsBindingObserver {
bool onScreen = true;
late MobileScannerController controller;
@override
void initState() {
super.initState();
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:
if (!controller.isStarting) controller.start();
break;
case AppLifecycleState.inactive:
case AppLifecycleState.paused:
case AppLifecycleState.detached:
controller.stop();
}
setState(() {
onScreen = false;
});
break;
}
}
@override
Widget build(BuildContext context) {
return LayoutBuilder(builder: (context, BoxConstraints constraints) {
if (!onScreen) return const Text("Camera Paused.");
return ValueListenableBuilder(
valueListenable: controller.args,
builder: (context, value, child) {
... ... @@ -114,7 +110,8 @@ class _MobileScannerState extends State<MobileScanner>
@override
void dispose() {
if (widget.controller == null) controller.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,9 +61,9 @@ 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,20 +93,27 @@ 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,
bool isStarting = false;
/// Start barcode scanning. This will first check if the required permissions
/// are set.
Future<void> start() async {
ensure('startAsync');
if (isStarting) {
throw Exception('mobile_scanner: Called start() while already starting.');
}
isStarting = true;
// setAnalyzeMode(AnalyzeMode.barcode.index);
setAnalyzeMode(AnalyzeMode.barcode.index);
// Check authorization status
MobileScannerState state =
MobileScannerState.values[await methodChannel.invokeMethod('state')];
... ... @@ -118,6 +124,7 @@ class MobileScannerController {
result ? MobileScannerState.authorized : MobileScannerState.denied;
break;
case MobileScannerState.denied:
isStarting = false;
throw PlatformException(code: 'NO ACCESS');
case MobileScannerState.authorized:
break;
... ... @@ -132,10 +139,19 @@ 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}');
isStarting = false;
// setAnalyzeMode(AnalyzeMode.none.index);
return;
}
if (startResult == null) {
isStarting = false;
throw PlatformException(code: 'INITIALIZATION ERROR');
}
... ... @@ -144,19 +160,35 @@ class MobileScannerController {
textureId: startResult['textureId'],
size: toSize(startResult['size']),
hasTorch: hasTorch);
isStarting = false;
}
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 +196,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();
}
Future<void> analyzeImage(dynamic path) async {
... ...
... ... @@ -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.
///
... ... @@ -165,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)
... ... @@ -202,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)
... ... @@ -215,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)
... ... @@ -263,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)
... ... @@ -355,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)
... ... @@ -383,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)
... ... @@ -399,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)
... ... @@ -417,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)
... ... @@ -435,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)
... ... @@ -458,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
... ... @@ -232,13 +252,19 @@ public class MobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHandler,
result(FlutterError(code: error.localizedDescription, message: nil, details: nil))
}
}
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 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.1
version: 0.1.3
repository: https://github.com/juliansteenbakker/mobile_scanner
environment:
... ...