Julian Steenbakker

Merge branch 'master' into web

# Conflicts:
#	lib/src/mobile_scanner.dart
  1 +## 0.1.2
  2 +* MobileScannerArguments is now exported. [#7](https://github.com/juliansteenbakker/mobile_scanner/issues/7)
  3 +
  4 +Bugfixes:
  5 +* Fixed application crashing when stop() or start() is called multiple times. [#5](https://github.com/juliansteenbakker/mobile_scanner/issues/5)
  6 +* Fixes controller not being disposed correctly. [#23](https://github.com/juliansteenbakker/mobile_scanner/issues/23)
  7 +* Catch error when no camera is found. [#19](https://github.com/juliansteenbakker/mobile_scanner/issues/19)
  8 +
  9 +## 0.1.1
  10 +mobile_scanner is now compatible with sdk >= 2.12 and flutter >= 2.2.0
  11 +
1 ## 0.1.0 12 ## 0.1.0
2 We now have MacOS support using Apple's Vision framework! 13 We now have MacOS support using Apple's Vision framework!
3 -Keep in mind that for now, only the raw value is supported. 14 +Keep in mind that for now, only the raw value of the barcode object is supported.
  15 +
  16 +Bugfixes:
  17 +* Fixed a crash when dispose is called in a overridden method. [#5](https://github.com/juliansteenbakker/mobile_scanner/issues/5)
4 18
5 ## 0.0.3 19 ## 0.0.3
6 * Added some API docs and README 20 * Added some API docs and README
@@ -3,13 +3,13 @@ @@ -3,13 +3,13 @@
3 [![pub package](https://img.shields.io/pub/v/mobile_scanner.svg)](https://pub.dev/packages/mobile_scanner) 3 [![pub package](https://img.shields.io/pub/v/mobile_scanner.svg)](https://pub.dev/packages/mobile_scanner)
4 [![mobile_scanner](https://github.com/juliansteenbakker/mobile_scanner/actions/workflows/flutter.yml/badge.svg)](https://github.com/juliansteenbakker/mobile_scanner/actions/workflows/flutter.yml) 4 [![mobile_scanner](https://github.com/juliansteenbakker/mobile_scanner/actions/workflows/flutter.yml/badge.svg)](https://github.com/juliansteenbakker/mobile_scanner/actions/workflows/flutter.yml)
5 5
6 -A universal scanner for Flutter based on MLKit. Uses CameraX on Android and AVFoundation on iOS. 6 +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.
7 7
8 ## Platform Support 8 ## Platform Support
9 9
10 | Android | iOS | MacOS | Web | Linux | Windows | 10 | Android | iOS | MacOS | Web | Linux | Windows |
11 | :-----: | :-: | :---: | :-: | :---: | :-----: | 11 | :-----: | :-: | :---: | :-: | :---: | :-----: |
12 -| ✔️ | ✔️ | | | | | 12 +| ✔️ | ✔️ | ✔️ | | | |
13 13
14 CameraX for Android requires at least SDK 21. 14 CameraX for Android requires at least SDK 21.
15 15
1 include: package:flutter_lints/flutter.yaml 1 include: package:flutter_lints/flutter.yaml
  2 +
  3 +linter:
  4 + rules:
  5 + unawaited_futures: true
@@ -38,8 +38,8 @@ class MobileScanner(private val activity: Activity, private val textureRegistry: @@ -38,8 +38,8 @@ class MobileScanner(private val activity: Activity, private val textureRegistry:
38 private var camera: Camera? = null 38 private var camera: Camera? = null
39 private var textureEntry: TextureRegistry.SurfaceTextureEntry? = null 39 private var textureEntry: TextureRegistry.SurfaceTextureEntry? = null
40 40
41 - @AnalyzeMode  
42 - private var analyzeMode: Int = AnalyzeMode.NONE 41 +// @AnalyzeMode
  42 +// private var analyzeMode: Int = AnalyzeMode.NONE
43 43
44 @ExperimentalGetImage 44 @ExperimentalGetImage
45 override fun onMethodCall(@NonNull call: MethodCall, @NonNull result: MethodChannel.Result) { 45 override fun onMethodCall(@NonNull call: MethodCall, @NonNull result: MethodChannel.Result) {
@@ -47,8 +47,8 @@ class MobileScanner(private val activity: Activity, private val textureRegistry: @@ -47,8 +47,8 @@ class MobileScanner(private val activity: Activity, private val textureRegistry:
47 "state" -> checkPermission(result) 47 "state" -> checkPermission(result)
48 "request" -> requestPermission(result) 48 "request" -> requestPermission(result)
49 "start" -> start(call, result) 49 "start" -> start(call, result)
50 - "torch" -> switchTorch(call, result)  
51 - "analyze" -> switchAnalyzeMode(call, result) 50 + "torch" -> toggleTorch(call, result)
  51 +// "analyze" -> switchAnalyzeMode(call, result)
52 "stop" -> stop(result) 52 "stop" -> stop(result)
53 else -> result.notImplemented() 53 else -> result.notImplemented()
54 } 54 }
@@ -92,8 +92,8 @@ class MobileScanner(private val activity: Activity, private val textureRegistry: @@ -92,8 +92,8 @@ class MobileScanner(private val activity: Activity, private val textureRegistry:
92 92
93 @ExperimentalGetImage 93 @ExperimentalGetImage
94 val analyzer = ImageAnalysis.Analyzer { imageProxy -> // YUV_420_888 format 94 val analyzer = ImageAnalysis.Analyzer { imageProxy -> // YUV_420_888 format
95 - when (analyzeMode) {  
96 - AnalyzeMode.BARCODE -> { 95 +// when (analyzeMode) {
  96 +// AnalyzeMode.BARCODE -> {
97 val mediaImage = imageProxy.image ?: return@Analyzer 97 val mediaImage = imageProxy.image ?: return@Analyzer
98 val inputImage = InputImage.fromMediaImage(mediaImage, imageProxy.imageInfo.rotationDegrees) 98 val inputImage = InputImage.fromMediaImage(mediaImage, imageProxy.imageInfo.rotationDegrees)
99 99
@@ -106,9 +106,9 @@ class MobileScanner(private val activity: Activity, private val textureRegistry: @@ -106,9 +106,9 @@ class MobileScanner(private val activity: Activity, private val textureRegistry:
106 } 106 }
107 .addOnFailureListener { e -> Log.e(TAG, e.message, e) } 107 .addOnFailureListener { e -> Log.e(TAG, e.message, e) }
108 .addOnCompleteListener { imageProxy.close() } 108 .addOnCompleteListener { imageProxy.close() }
109 - }  
110 - else -> imageProxy.close()  
111 - } 109 +// }
  110 +// else -> imageProxy.close()
  111 +// }
112 } 112 }
113 113
114 114
@@ -116,6 +116,10 @@ class MobileScanner(private val activity: Activity, private val textureRegistry: @@ -116,6 +116,10 @@ class MobileScanner(private val activity: Activity, private val textureRegistry:
116 116
117 @ExperimentalGetImage 117 @ExperimentalGetImage
118 private fun start(call: MethodCall, result: MethodChannel.Result) { 118 private fun start(call: MethodCall, result: MethodChannel.Result) {
  119 + if (camera != null) {
  120 + result.error(TAG, "Called start() while already started!", null)
  121 + return
  122 + }
119 123
120 val facing: Int = call.argument<Int>("facing") ?: 0 124 val facing: Int = call.argument<Int>("facing") ?: 0
121 val ratio: Int? = call.argument<Int>("ratio") 125 val ratio: Int? = call.argument<Int>("ratio")
@@ -186,24 +190,32 @@ class MobileScanner(private val activity: Activity, private val textureRegistry: @@ -186,24 +190,32 @@ class MobileScanner(private val activity: Activity, private val textureRegistry:
186 }, executor) 190 }, executor)
187 } 191 }
188 192
189 - private fun switchTorch(call: MethodCall, result: MethodChannel.Result) {  
190 - val state = call.arguments == 1  
191 - camera!!.cameraControl.enableTorch(state)  
192 - result.success(null) 193 + private fun toggleTorch(call: MethodCall, result: MethodChannel.Result) {
  194 + if (camera == null) {
  195 + result.error(TAG,"Called toggleTorch() while stopped!", null)
  196 + return
193 } 197 }
194 -  
195 - private fun switchAnalyzeMode(call: MethodCall, result: MethodChannel.Result) {  
196 - analyzeMode = call.arguments as Int 198 + camera!!.cameraControl.enableTorch(call.arguments == 1)
197 result.success(null) 199 result.success(null)
198 } 200 }
199 201
  202 +// private fun switchAnalyzeMode(call: MethodCall, result: MethodChannel.Result) {
  203 +// analyzeMode = call.arguments as Int
  204 +// result.success(null)
  205 +// }
  206 +
200 private fun stop(result: MethodChannel.Result) { 207 private fun stop(result: MethodChannel.Result) {
  208 + if (camera == null) {
  209 + result.error(TAG,"Called stop() while already stopped!", null)
  210 + return
  211 + }
  212 +
201 val owner = activity as LifecycleOwner 213 val owner = activity as LifecycleOwner
202 camera!!.cameraInfo.torchState.removeObservers(owner) 214 camera!!.cameraInfo.torchState.removeObservers(owner)
203 cameraProvider!!.unbindAll() 215 cameraProvider!!.unbindAll()
204 textureEntry!!.release() 216 textureEntry!!.release()
205 217
206 - analyzeMode = AnalyzeMode.NONE 218 +// analyzeMode = AnalyzeMode.NONE
207 camera = null 219 camera = null
208 textureEntry = null 220 textureEntry = null
209 cameraProvider = null 221 cameraProvider = null
  1 +import 'package:flutter/material.dart';
  2 +import 'package:mobile_scanner/mobile_scanner.dart';
  3 +
  4 +class BarcodeScannerWithController extends StatefulWidget {
  5 + const BarcodeScannerWithController({Key? key}) : super(key: key);
  6 +
  7 + @override
  8 + _BarcodeScannerWithControllerState createState() =>
  9 + _BarcodeScannerWithControllerState();
  10 +}
  11 +
  12 +class _BarcodeScannerWithControllerState
  13 + extends State<BarcodeScannerWithController>
  14 + with SingleTickerProviderStateMixin {
  15 + String? barcode;
  16 +
  17 + MobileScannerController controller = MobileScannerController(
  18 + torchEnabled: true,
  19 + // facing: CameraFacing.front,
  20 + );
  21 +
  22 + @override
  23 + Widget build(BuildContext context) {
  24 + return MaterialApp(
  25 + home: Scaffold(
  26 + backgroundColor: Colors.black,
  27 + body: Builder(builder: (context) {
  28 + return Stack(
  29 + children: [
  30 + MobileScanner(
  31 + controller: controller,
  32 + fit: BoxFit.contain,
  33 + // controller: MobileScannerController(
  34 + // torchEnabled: true,
  35 + // facing: CameraFacing.front,
  36 + // ),
  37 + onDetect: (barcode, args) {
  38 + if (this.barcode != barcode.rawValue) {
  39 + setState(() {
  40 + this.barcode = barcode.rawValue;
  41 + });
  42 + }
  43 + }),
  44 + Align(
  45 + alignment: Alignment.bottomCenter,
  46 + child: Container(
  47 + alignment: Alignment.bottomCenter,
  48 + height: 100,
  49 + color: Colors.black.withOpacity(0.4),
  50 + child: Row(
  51 + crossAxisAlignment: CrossAxisAlignment.center,
  52 + mainAxisAlignment: MainAxisAlignment.spaceEvenly,
  53 + children: [
  54 + IconButton(
  55 + color: Colors.white,
  56 + icon: ValueListenableBuilder(
  57 + valueListenable: controller.torchState,
  58 + builder: (context, state, child) {
  59 + switch (state as TorchState) {
  60 + case TorchState.off:
  61 + return const Icon(Icons.flash_off,
  62 + color: Colors.grey);
  63 + case TorchState.on:
  64 + return const Icon(Icons.flash_on,
  65 + color: Colors.yellow);
  66 + }
  67 + },
  68 + ),
  69 + iconSize: 32.0,
  70 + onPressed: () => controller.toggleTorch(),
  71 + ),
  72 + Center(
  73 + child: SizedBox(
  74 + width: MediaQuery.of(context).size.width - 120,
  75 + height: 50,
  76 + child: FittedBox(
  77 + child: Text(
  78 + barcode ?? 'Scan something!',
  79 + overflow: TextOverflow.fade,
  80 + style: Theme.of(context)
  81 + .textTheme
  82 + .headline4!
  83 + .copyWith(color: Colors.white),
  84 + ),
  85 + ),
  86 + ),
  87 + ),
  88 + IconButton(
  89 + color: Colors.white,
  90 + icon: ValueListenableBuilder(
  91 + valueListenable: controller.cameraFacingState,
  92 + builder: (context, state, child) {
  93 + switch (state as CameraFacing) {
  94 + case CameraFacing.front:
  95 + return const Icon(Icons.camera_front);
  96 + case CameraFacing.back:
  97 + return const Icon(Icons.camera_rear);
  98 + }
  99 + },
  100 + ),
  101 + iconSize: 32.0,
  102 + onPressed: () => controller.switchCamera(),
  103 + ),
  104 + ],
  105 + ),
  106 + ),
  107 + ),
  108 + ],
  109 + );
  110 + }),
  111 + ),
  112 + );
  113 + }
  114 +}
  1 +import 'package:flutter/material.dart';
  2 +import 'package:mobile_scanner/mobile_scanner.dart';
  3 +
  4 +class BarcodeScannerWithoutController extends StatefulWidget {
  5 + const BarcodeScannerWithoutController({Key? key}) : super(key: key);
  6 +
  7 + @override
  8 + _BarcodeScannerWithoutControllerState createState() =>
  9 + _BarcodeScannerWithoutControllerState();
  10 +}
  11 +
  12 +class _BarcodeScannerWithoutControllerState
  13 + extends State<BarcodeScannerWithoutController>
  14 + with SingleTickerProviderStateMixin {
  15 + String? barcode;
  16 +
  17 + @override
  18 + Widget build(BuildContext context) {
  19 + return MaterialApp(
  20 + home: Scaffold(
  21 + backgroundColor: Colors.black,
  22 + body: Builder(builder: (context) {
  23 + return Stack(
  24 + children: [
  25 + MobileScanner(
  26 + fit: BoxFit.contain,
  27 + onDetect: (barcode, args) {
  28 + if (this.barcode != barcode.rawValue) {
  29 + setState(() {
  30 + this.barcode = barcode.rawValue;
  31 + });
  32 + }
  33 + }),
  34 + Align(
  35 + alignment: Alignment.bottomCenter,
  36 + child: Container(
  37 + alignment: Alignment.bottomCenter,
  38 + height: 100,
  39 + color: Colors.black.withOpacity(0.4),
  40 + child: Row(
  41 + crossAxisAlignment: CrossAxisAlignment.center,
  42 + mainAxisAlignment: MainAxisAlignment.spaceEvenly,
  43 + children: [
  44 + Center(
  45 + child: SizedBox(
  46 + width: MediaQuery.of(context).size.width - 120,
  47 + height: 50,
  48 + child: FittedBox(
  49 + child: Text(
  50 + barcode ?? 'Scan something!',
  51 + overflow: TextOverflow.fade,
  52 + style: Theme.of(context)
  53 + .textTheme
  54 + .headline4!
  55 + .copyWith(color: Colors.white),
  56 + ),
  57 + ),
  58 + ),
  59 + ),
  60 + ],
  61 + ),
  62 + ),
  63 + ),
  64 + ],
  65 + );
  66 + }),
  67 + ),
  68 + );
  69 + }
  70 +}
1 import 'package:flutter/material.dart'; 1 import 'package:flutter/material.dart';
2 -import 'package:mobile_scanner/mobile_scanner.dart'; 2 +import 'package:mobile_scanner_example/barcode_scanner_controller.dart';
  3 +import 'package:mobile_scanner_example/barcode_scanner_without_controller.dart';
3 4
4 -void main() {  
5 - runApp(const AnalyzeView());  
6 -}  
7 -  
8 -class AnalyzeView extends StatefulWidget {  
9 - const AnalyzeView({Key? key}) : super(key: key); 5 +void main() => runApp(const MaterialApp(home: MyHome()));
10 6
11 - @override  
12 - _AnalyzeViewState createState() => _AnalyzeViewState();  
13 -}  
14 -  
15 -class _AnalyzeViewState extends State<AnalyzeView>  
16 - with SingleTickerProviderStateMixin {  
17 - String? barcode;  
18 -  
19 - MobileScannerController controller = MobileScannerController(  
20 - torchEnabled: true,  
21 - facing: CameraFacing.front,  
22 - ); 7 +class MyHome extends StatelessWidget {
  8 + const MyHome({Key? key}) : super(key: key);
23 9
24 @override 10 @override
25 Widget build(BuildContext context) { 11 Widget build(BuildContext context) {
26 - return MaterialApp(  
27 - home: Scaffold(  
28 - backgroundColor: Colors.black,  
29 - body: Builder(builder: (context) {  
30 - return Stack(  
31 - children: [  
32 - MobileScanner(  
33 - controller: controller,  
34 - fit: BoxFit.contain,  
35 - // controller: MobileScannerController(  
36 - // torchEnabled: true,  
37 - // facing: CameraFacing.front,  
38 - // ),  
39 - onDetect: (barcode, args) {  
40 - if (this.barcode != barcode.rawValue) {  
41 - setState(() {  
42 - this.barcode = barcode.rawValue;  
43 - });  
44 - }  
45 - }),  
46 - Align(  
47 - alignment: Alignment.bottomCenter,  
48 - child: Container(  
49 - alignment: Alignment.bottomCenter,  
50 - height: 100,  
51 - color: Colors.black.withOpacity(0.4),  
52 - child: Row( 12 + return Scaffold(
  13 + appBar: AppBar(title: const Text('Flutter Demo Home Page')),
  14 + body: SizedBox(
  15 + width: MediaQuery.of(context).size.width,
  16 + height: MediaQuery.of(context).size.height,
  17 + child: Column(
  18 + mainAxisAlignment: MainAxisAlignment.center,
53 crossAxisAlignment: CrossAxisAlignment.center, 19 crossAxisAlignment: CrossAxisAlignment.center,
54 - mainAxisAlignment: MainAxisAlignment.spaceEvenly,  
55 children: [ 20 children: [
56 - IconButton(  
57 - color: Colors.white,  
58 - icon: ValueListenableBuilder(  
59 - valueListenable: controller.torchState,  
60 - builder: (context, state, child) {  
61 - switch (state as TorchState) {  
62 - case TorchState.off:  
63 - return const Icon(Icons.flash_off,  
64 - color: Colors.grey);  
65 - case TorchState.on:  
66 - return const Icon(Icons.flash_on,  
67 - color: Colors.yellow);  
68 - } 21 + ElevatedButton(
  22 + onPressed: () {
  23 + Navigator.of(context).push(MaterialPageRoute(
  24 + builder: (context) => const BarcodeScannerWithController(),
  25 + ));
69 }, 26 },
  27 + child: const Text('MobileScanner with Controller'),
70 ), 28 ),
71 - iconSize: 32.0,  
72 - onPressed: () => controller.toggleTorch(),  
73 - ),  
74 - Center(  
75 - child: SizedBox(  
76 - width: MediaQuery.of(context).size.width - 120,  
77 - height: 50,  
78 - child: FittedBox(  
79 - child: Text(  
80 - barcode ?? 'Scan something!',  
81 - overflow: TextOverflow.fade,  
82 - style: Theme.of(context)  
83 - .textTheme  
84 - .headline4!  
85 - .copyWith(color: Colors.white),  
86 - ),  
87 - ),  
88 - ),  
89 - ),  
90 - IconButton(  
91 - color: Colors.white,  
92 - icon: ValueListenableBuilder(  
93 - valueListenable: controller.cameraFacingState,  
94 - builder: (context, state, child) {  
95 - switch (state as CameraFacing) {  
96 - case CameraFacing.front:  
97 - return const Icon(Icons.camera_front);  
98 - case CameraFacing.back:  
99 - return const Icon(Icons.camera_rear);  
100 - } 29 + ElevatedButton(
  30 + onPressed: () {
  31 + Navigator.of(context).push(MaterialPageRoute(
  32 + builder: (context) => const BarcodeScannerWithoutController(),
  33 + ));
101 }, 34 },
102 - ),  
103 - iconSize: 32.0,  
104 - onPressed: () => controller.switchCamera(), 35 + child: const Text('MobileScanner without Controller'),
105 ), 36 ),
106 ], 37 ],
107 ), 38 ),
108 ), 39 ),
109 - ),  
110 -  
111 - // Container(  
112 - // alignment: Alignment.bottomCenter,  
113 - // margin: EdgeInsets.only(bottom: 80.0),  
114 - // child: IconButton(  
115 - // icon: ValueListenableBuilder(  
116 - // valueListenable: cameraController.torchState,  
117 - // builder: (context, state, child) {  
118 - // final color =  
119 - // state == TorchState.off ? Colors.grey : Colors.white;  
120 - // return Icon(Icons.bolt, color: color);  
121 - // },  
122 - // ),  
123 - // iconSize: 32.0,  
124 - // onPressed: () => cameraController.torch(),  
125 - // ),  
126 - // ),  
127 - ],  
128 - );  
129 - }),  
130 - ),  
131 ); 40 );
132 } 41 }
133 -  
134 - @override  
135 - void dispose() {  
136 - // cameraController.dispose();  
137 - super.dispose();  
138 - }  
139 -  
140 - void display(Barcode barcode) {  
141 - Navigator.of(context).popAndPushNamed('display', arguments: barcode);  
142 - }  
143 } 42 }
144 -  
145 -// import 'package:flutter/material.dart';  
146 -// import 'package:flutter/rendering.dart';  
147 -// import 'package:mobile_scanner/mobile_scanner.dart';  
148 -//  
149 -// void main() {  
150 -// debugPaintSizeEnabled = false;  
151 -// runApp(HomePage());  
152 -// }  
153 -//  
154 -// class HomePage extends StatefulWidget {  
155 -// @override  
156 -// HomeState createState() => HomeState();  
157 -// }  
158 -//  
159 -// class HomeState extends State<HomePage> {  
160 -// @override  
161 -// Widget build(BuildContext context) {  
162 -// return MaterialApp(home: MyApp());  
163 -// }  
164 -// }  
165 -//  
166 -// class MyApp extends StatefulWidget {  
167 -// @override  
168 -// _MyAppState createState() => _MyAppState();  
169 -// }  
170 -//  
171 -// class _MyAppState extends State<MyApp> {  
172 -// String? qr;  
173 -// bool camState = false;  
174 -//  
175 -// @override  
176 -// initState() {  
177 -// super.initState();  
178 -// }  
179 -//  
180 -// @override  
181 -// Widget build(BuildContext context) {  
182 -// return Scaffold(  
183 -// appBar: AppBar(  
184 -// title: Text('Plugin example app'),  
185 -// ),  
186 -// body: Center(  
187 -// child: Column(  
188 -// crossAxisAlignment: CrossAxisAlignment.center,  
189 -// mainAxisAlignment: MainAxisAlignment.center,  
190 -// children: <Widget>[  
191 -// Expanded(  
192 -// child: camState  
193 -// ? Center(  
194 -// child: SizedBox(  
195 -// width: 300.0,  
196 -// height: 600.0,  
197 -// child: MobileScanner(  
198 -// onError: (context, error) => Text(  
199 -// error.toString(),  
200 -// style: TextStyle(color: Colors.red),  
201 -// ),  
202 -// qrCodeCallback: (code) {  
203 -// setState(() {  
204 -// qr = code;  
205 -// });  
206 -// },  
207 -// child: Container(  
208 -// decoration: BoxDecoration(  
209 -// color: Colors.transparent,  
210 -// border: Border.all(  
211 -// color: Colors.orange,  
212 -// width: 10.0,  
213 -// style: BorderStyle.solid),  
214 -// ),  
215 -// ),  
216 -// ),  
217 -// ),  
218 -// )  
219 -// : Center(child: Text("Camera inactive"))),  
220 -// Text("QRCODE: $qr"),  
221 -// ],  
222 -// ),  
223 -// ),  
224 -// floatingActionButton: FloatingActionButton(  
225 -// child: Text(  
226 -// "press me",  
227 -// textAlign: TextAlign.center,  
228 -// ),  
229 -// onPressed: () {  
230 -// setState(() {  
231 -// camState = !camState;  
232 -// });  
233 -// }),  
234 -// );  
235 -// }  
236 -// }  
1 -platform :osx, '10.11' 1 +platform :osx, '10.13'
2 2
3 # CocoaPods analytics sends network stats synchronously affecting flutter build latency. 3 # CocoaPods analytics sends network stats synchronously affecting flutter build latency.
4 ENV['COCOAPODS_DISABLE_STATS'] = 'true' 4 ENV['COCOAPODS_DISABLE_STATS'] = 'true'
@@ -26,7 +26,7 @@ @@ -26,7 +26,7 @@
26 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F22044A3C60003C045 /* Assets.xcassets */; }; 26 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F22044A3C60003C045 /* Assets.xcassets */; };
27 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F42044A3C60003C045 /* MainMenu.xib */; }; 27 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F42044A3C60003C045 /* MainMenu.xib */; };
28 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */; }; 28 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */; };
29 - 5225F51353DA345E2811B6A4 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 65E614A1DF8B88C7B0CE1B97 /* Pods_Runner.framework */; }; 29 + 5B9BD2ADBC68B74D80B57DF1 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = EC099C2B6D6B30BFB3FA6DB8 /* Pods_Runner.framework */; };
30 /* End PBXBuildFile section */ 30 /* End PBXBuildFile section */
31 31
32 /* Begin PBXContainerItemProxy section */ 32 /* Begin PBXContainerItemProxy section */
@@ -67,12 +67,12 @@ @@ -67,12 +67,12 @@
67 33E51913231747F40026EE4D /* DebugProfile.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = DebugProfile.entitlements; sourceTree = "<group>"; }; 67 33E51913231747F40026EE4D /* DebugProfile.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = DebugProfile.entitlements; sourceTree = "<group>"; };
68 33E51914231749380026EE4D /* Release.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = Release.entitlements; sourceTree = "<group>"; }; 68 33E51914231749380026EE4D /* Release.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = Release.entitlements; sourceTree = "<group>"; };
69 33E5194F232828860026EE4D /* AppInfo.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = AppInfo.xcconfig; sourceTree = "<group>"; }; 69 33E5194F232828860026EE4D /* AppInfo.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = AppInfo.xcconfig; sourceTree = "<group>"; };
70 - 65E614A1DF8B88C7B0CE1B97 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 70 + 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>"; };
71 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = "<group>"; }; 71 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = "<group>"; };
72 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = "<group>"; }; 72 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = "<group>"; };
73 - 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>"; };  
74 - 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>"; };  
75 - 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>"; }; 73 + 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>"; };
  74 + 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>"; };
  75 + EC099C2B6D6B30BFB3FA6DB8 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; };
76 /* End PBXFileReference section */ 76 /* End PBXFileReference section */
77 77
78 /* Begin PBXFrameworksBuildPhase section */ 78 /* Begin PBXFrameworksBuildPhase section */
@@ -80,19 +80,27 @@ @@ -80,19 +80,27 @@
80 isa = PBXFrameworksBuildPhase; 80 isa = PBXFrameworksBuildPhase;
81 buildActionMask = 2147483647; 81 buildActionMask = 2147483647;
82 files = ( 82 files = (
83 - 5225F51353DA345E2811B6A4 /* Pods_Runner.framework in Frameworks */, 83 + 5B9BD2ADBC68B74D80B57DF1 /* Pods_Runner.framework in Frameworks */,
84 ); 84 );
85 runOnlyForDeploymentPostprocessing = 0; 85 runOnlyForDeploymentPostprocessing = 0;
86 }; 86 };
87 /* End PBXFrameworksBuildPhase section */ 87 /* End PBXFrameworksBuildPhase section */
88 88
89 /* Begin PBXGroup section */ 89 /* Begin PBXGroup section */
  90 + 18927D60C719EB75FC0A6633 /* Frameworks */ = {
  91 + isa = PBXGroup;
  92 + children = (
  93 + EC099C2B6D6B30BFB3FA6DB8 /* Pods_Runner.framework */,
  94 + );
  95 + name = Frameworks;
  96 + sourceTree = "<group>";
  97 + };
90 20F8C9AA20C2A495C125E194 /* Pods */ = { 98 20F8C9AA20C2A495C125E194 /* Pods */ = {
91 isa = PBXGroup; 99 isa = PBXGroup;
92 children = ( 100 children = (
93 - CB0901144E09E7D7CA20584F /* Pods-Runner.debug.xcconfig */,  
94 - D522F9F6F348C5944077606B /* Pods-Runner.release.xcconfig */,  
95 - F63009B5E287A1C82F9D7D2F /* Pods-Runner.profile.xcconfig */, 101 + CAD760C57A57D903AB03B47A /* Pods-Runner.debug.xcconfig */,
  102 + A1CBC07680A8ED396DBB68C0 /* Pods-Runner.release.xcconfig */,
  103 + 3CEE8DB43A84811F33EB0202 /* Pods-Runner.profile.xcconfig */,
96 ); 104 );
97 path = Pods; 105 path = Pods;
98 sourceTree = "<group>"; 106 sourceTree = "<group>";
@@ -115,7 +123,7 @@ @@ -115,7 +123,7 @@
115 33CEB47122A05771004F2AC0 /* Flutter */, 123 33CEB47122A05771004F2AC0 /* Flutter */,
116 33CC10EE2044A3C60003C045 /* Products */, 124 33CC10EE2044A3C60003C045 /* Products */,
117 20F8C9AA20C2A495C125E194 /* Pods */, 125 20F8C9AA20C2A495C125E194 /* Pods */,
118 - 3539353E79638640B4999C09 /* Frameworks */, 126 + 18927D60C719EB75FC0A6633 /* Frameworks */,
119 ); 127 );
120 sourceTree = "<group>"; 128 sourceTree = "<group>";
121 }; 129 };
@@ -162,14 +170,6 @@ @@ -162,14 +170,6 @@
162 path = Runner; 170 path = Runner;
163 sourceTree = "<group>"; 171 sourceTree = "<group>";
164 }; 172 };
165 - 3539353E79638640B4999C09 /* Frameworks */ = {  
166 - isa = PBXGroup;  
167 - children = (  
168 - 65E614A1DF8B88C7B0CE1B97 /* Pods_Runner.framework */,  
169 - );  
170 - name = Frameworks;  
171 - sourceTree = "<group>";  
172 - };  
173 /* End PBXGroup section */ 173 /* End PBXGroup section */
174 174
175 /* Begin PBXNativeTarget section */ 175 /* Begin PBXNativeTarget section */
@@ -177,13 +177,13 @@ @@ -177,13 +177,13 @@
177 isa = PBXNativeTarget; 177 isa = PBXNativeTarget;
178 buildConfigurationList = 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */; 178 buildConfigurationList = 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */;
179 buildPhases = ( 179 buildPhases = (
180 - 696298230BDAD783AEC51C81 /* [CP] Check Pods Manifest.lock */, 180 + 20903D1E9D9F08576541FFD7 /* [CP] Check Pods Manifest.lock */,
181 33CC10E92044A3C60003C045 /* Sources */, 181 33CC10E92044A3C60003C045 /* Sources */,
182 33CC10EA2044A3C60003C045 /* Frameworks */, 182 33CC10EA2044A3C60003C045 /* Frameworks */,
183 33CC10EB2044A3C60003C045 /* Resources */, 183 33CC10EB2044A3C60003C045 /* Resources */,
184 33CC110E2044A8840003C045 /* Bundle Framework */, 184 33CC110E2044A8840003C045 /* Bundle Framework */,
185 3399D490228B24CF009A79C7 /* ShellScript */, 185 3399D490228B24CF009A79C7 /* ShellScript */,
186 - 8A90D2BC4083C5ACCEEBF32B /* [CP] Embed Pods Frameworks */, 186 + DF45614760BB9B24F49B2055 /* [CP] Embed Pods Frameworks */,
187 ); 187 );
188 buildRules = ( 188 buildRules = (
189 ); 189 );
@@ -253,7 +253,7 @@ @@ -253,7 +253,7 @@
253 /* End PBXResourcesBuildPhase section */ 253 /* End PBXResourcesBuildPhase section */
254 254
255 /* Begin PBXShellScriptBuildPhase section */ 255 /* Begin PBXShellScriptBuildPhase section */
256 - 3399D490228B24CF009A79C7 /* ShellScript */ = { 256 + 20903D1E9D9F08576541FFD7 /* [CP] Check Pods Manifest.lock */ = {
257 isa = PBXShellScriptBuildPhase; 257 isa = PBXShellScriptBuildPhase;
258 buildActionMask = 2147483647; 258 buildActionMask = 2147483647;
259 files = ( 259 files = (
@@ -261,58 +261,58 @@ @@ -261,58 +261,58 @@
261 inputFileListPaths = ( 261 inputFileListPaths = (
262 ); 262 );
263 inputPaths = ( 263 inputPaths = (
  264 + "${PODS_PODFILE_DIR_PATH}/Podfile.lock",
  265 + "${PODS_ROOT}/Manifest.lock",
264 ); 266 );
  267 + name = "[CP] Check Pods Manifest.lock";
265 outputFileListPaths = ( 268 outputFileListPaths = (
266 ); 269 );
267 outputPaths = ( 270 outputPaths = (
  271 + "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt",
268 ); 272 );
269 runOnlyForDeploymentPostprocessing = 0; 273 runOnlyForDeploymentPostprocessing = 0;
270 shellPath = /bin/sh; 274 shellPath = /bin/sh;
271 - shellScript = "echo \"$PRODUCT_NAME.app\" > \"$PROJECT_DIR\"/Flutter/ephemeral/.app_filename && \"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh embed\n"; 275 + 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";
  276 + showEnvVarsInLog = 0;
272 }; 277 };
273 - 33CC111E2044C6BF0003C045 /* ShellScript */ = { 278 + 3399D490228B24CF009A79C7 /* ShellScript */ = {
274 isa = PBXShellScriptBuildPhase; 279 isa = PBXShellScriptBuildPhase;
275 buildActionMask = 2147483647; 280 buildActionMask = 2147483647;
276 files = ( 281 files = (
277 ); 282 );
278 inputFileListPaths = ( 283 inputFileListPaths = (
279 - Flutter/ephemeral/FlutterInputs.xcfilelist,  
280 ); 284 );
281 inputPaths = ( 285 inputPaths = (
282 - Flutter/ephemeral/tripwire,  
283 ); 286 );
284 outputFileListPaths = ( 287 outputFileListPaths = (
285 - Flutter/ephemeral/FlutterOutputs.xcfilelist,  
286 ); 288 );
287 outputPaths = ( 289 outputPaths = (
288 ); 290 );
289 runOnlyForDeploymentPostprocessing = 0; 291 runOnlyForDeploymentPostprocessing = 0;
290 shellPath = /bin/sh; 292 shellPath = /bin/sh;
291 - shellScript = "\"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh && touch Flutter/ephemeral/tripwire"; 293 + shellScript = "echo \"$PRODUCT_NAME.app\" > \"$PROJECT_DIR\"/Flutter/ephemeral/.app_filename && \"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh embed\n";
292 }; 294 };
293 - 696298230BDAD783AEC51C81 /* [CP] Check Pods Manifest.lock */ = { 295 + 33CC111E2044C6BF0003C045 /* ShellScript */ = {
294 isa = PBXShellScriptBuildPhase; 296 isa = PBXShellScriptBuildPhase;
295 buildActionMask = 2147483647; 297 buildActionMask = 2147483647;
296 files = ( 298 files = (
297 ); 299 );
298 inputFileListPaths = ( 300 inputFileListPaths = (
  301 + Flutter/ephemeral/FlutterInputs.xcfilelist,
299 ); 302 );
300 inputPaths = ( 303 inputPaths = (
301 - "${PODS_PODFILE_DIR_PATH}/Podfile.lock",  
302 - "${PODS_ROOT}/Manifest.lock", 304 + Flutter/ephemeral/tripwire,
303 ); 305 );
304 - name = "[CP] Check Pods Manifest.lock";  
305 outputFileListPaths = ( 306 outputFileListPaths = (
  307 + Flutter/ephemeral/FlutterOutputs.xcfilelist,
306 ); 308 );
307 outputPaths = ( 309 outputPaths = (
308 - "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt",  
309 ); 310 );
310 runOnlyForDeploymentPostprocessing = 0; 311 runOnlyForDeploymentPostprocessing = 0;
311 shellPath = /bin/sh; 312 shellPath = /bin/sh;
312 - 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";  
313 - showEnvVarsInLog = 0; 313 + shellScript = "\"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh && touch Flutter/ephemeral/tripwire";
314 }; 314 };
315 - 8A90D2BC4083C5ACCEEBF32B /* [CP] Embed Pods Frameworks */ = { 315 + DF45614760BB9B24F49B2055 /* [CP] Embed Pods Frameworks */ = {
316 isa = PBXShellScriptBuildPhase; 316 isa = PBXShellScriptBuildPhase;
317 buildActionMask = 2147483647; 317 buildActionMask = 2147483647;
318 files = ( 318 files = (
@@ -3,7 +3,7 @@ description: Demonstrates how to use the mobile_scanner plugin. @@ -3,7 +3,7 @@ description: Demonstrates how to use the mobile_scanner plugin.
3 publish_to: 'none' # Remove this line if you wish to publish to pub.dev 3 publish_to: 'none' # Remove this line if you wish to publish to pub.dev
4 4
5 environment: 5 environment:
6 - sdk: ">=2.16.0 <3.0.0" 6 + sdk: ">=2.12.0 <3.0.0"
7 7
8 dependencies: 8 dependencies:
9 flutter: 9 flutter:
@@ -23,7 +23,7 @@ public class SwiftMobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHan @@ -23,7 +23,7 @@ public class SwiftMobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHan
23 var latestBuffer: CVImageBuffer! 23 var latestBuffer: CVImageBuffer!
24 24
25 25
26 - var analyzeMode: Int = 0 26 +// var analyzeMode: Int = 0
27 var analyzing: Bool = false 27 var analyzing: Bool = false
28 var position = AVCaptureDevice.Position.back 28 var position = AVCaptureDevice.Position.back
29 29
@@ -53,9 +53,9 @@ public class SwiftMobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHan @@ -53,9 +53,9 @@ public class SwiftMobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHan
53 case "start": 53 case "start":
54 start(call, result) 54 start(call, result)
55 case "torch": 55 case "torch":
56 - switchTorch(call, result)  
57 - case "analyze":  
58 - switchAnalyzeMode(call, result) 56 + toggleTorch(call, result)
  57 +// case "analyze":
  58 +// switchAnalyzeMode(call, result)
59 case "stop": 59 case "stop":
60 stop(result) 60 stop(result)
61 default: 61 default:
@@ -89,10 +89,10 @@ public class SwiftMobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHan @@ -89,10 +89,10 @@ public class SwiftMobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHan
89 latestBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) 89 latestBuffer = CMSampleBufferGetImageBuffer(sampleBuffer)
90 registry.textureFrameAvailable(textureId) 90 registry.textureFrameAvailable(textureId)
91 91
92 - switch analyzeMode {  
93 - case 1: // barcode 92 +// switch analyzeMode {
  93 +// case 1: // barcode
94 if analyzing { 94 if analyzing {
95 - break 95 + return
96 } 96 }
97 analyzing = true 97 analyzing = true
98 let buffer = CMSampleBufferGetImageBuffer(sampleBuffer) 98 let buffer = CMSampleBufferGetImageBuffer(sampleBuffer)
@@ -112,9 +112,9 @@ public class SwiftMobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHan @@ -112,9 +112,9 @@ public class SwiftMobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHan
112 } 112 }
113 analyzing = false 113 analyzing = false
114 } 114 }
115 - default: // none  
116 - break  
117 - } 115 +// default: // none
  116 +// break
  117 +// }
118 } 118 }
119 119
120 func imageOrientation( 120 func imageOrientation(
@@ -154,6 +154,13 @@ public class SwiftMobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHan @@ -154,6 +154,13 @@ public class SwiftMobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHan
154 } 154 }
155 155
156 func start(_ call: FlutterMethodCall, _ result: @escaping FlutterResult) { 156 func start(_ call: FlutterMethodCall, _ result: @escaping FlutterResult) {
  157 + if (device != nil) {
  158 + result(FlutterError(code: "MobileScanner",
  159 + message: "Called start() while already started!",
  160 + details: nil))
  161 + return
  162 + }
  163 +
157 textureId = registry.register(self) 164 textureId = registry.register(self)
158 captureSession = AVCaptureSession() 165 captureSession = AVCaptureSession()
159 166
@@ -173,6 +180,13 @@ public class SwiftMobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHan @@ -173,6 +180,13 @@ public class SwiftMobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHan
173 device = AVCaptureDevice.devices(for: .video).filter({$0.position == position}).first 180 device = AVCaptureDevice.devices(for: .video).filter({$0.position == position}).first
174 } 181 }
175 182
  183 + if (device == nil) {
  184 + result(FlutterError(code: "MobileScanner",
  185 + message: "No camera found or failed to open camera!",
  186 + details: nil))
  187 + return
  188 + }
  189 +
176 // Enable the torch if parameter is set and torch is available 190 // Enable the torch if parameter is set and torch is available
177 if (device.hasTorch && device.isTorchAvailable) { 191 if (device.hasTorch && device.isTorchAvailable) {
178 do { 192 do {
@@ -219,7 +233,13 @@ public class SwiftMobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHan @@ -219,7 +233,13 @@ public class SwiftMobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHan
219 result(answer) 233 result(answer)
220 } 234 }
221 235
222 - func switchTorch(_ call: FlutterMethodCall, _ result: @escaping FlutterResult) { 236 + func toggleTorch(_ call: FlutterMethodCall, _ result: @escaping FlutterResult) {
  237 + if (device == nil) {
  238 + result(FlutterError(code: "MobileScanner",
  239 + message: "Called toggleTorch() while stopped!",
  240 + details: nil))
  241 + return
  242 + }
223 do { 243 do {
224 try device.lockForConfiguration() 244 try device.lockForConfiguration()
225 device.torchMode = call.arguments as! Int == 1 ? .on : .off 245 device.torchMode = call.arguments as! Int == 1 ? .on : .off
@@ -230,12 +250,18 @@ public class SwiftMobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHan @@ -230,12 +250,18 @@ public class SwiftMobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHan
230 } 250 }
231 } 251 }
232 252
233 - func switchAnalyzeMode(_ call: FlutterMethodCall, _ result: @escaping FlutterResult) {  
234 - analyzeMode = call.arguments as! Int  
235 - result(nil)  
236 - } 253 +// func switchAnalyzeMode(_ call: FlutterMethodCall, _ result: @escaping FlutterResult) {
  254 +// analyzeMode = call.arguments as! Int
  255 +// result(nil)
  256 +// }
237 257
238 func stop(_ result: FlutterResult) { 258 func stop(_ result: FlutterResult) {
  259 + if (device == nil) {
  260 + result(FlutterError(code: "MobileScanner",
  261 + message: "Called stop() while already stopped!",
  262 + details: nil))
  263 + return
  264 + }
239 captureSession.stopRunning() 265 captureSession.stopRunning()
240 for input in captureSession.inputs { 266 for input in captureSession.inputs {
241 captureSession.removeInput(input) 267 captureSession.removeInput(input)
@@ -246,7 +272,7 @@ public class SwiftMobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHan @@ -246,7 +272,7 @@ public class SwiftMobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHan
246 device.removeObserver(self, forKeyPath: #keyPath(AVCaptureDevice.torchMode)) 272 device.removeObserver(self, forKeyPath: #keyPath(AVCaptureDevice.torchMode))
247 registry.unregisterTexture(textureId) 273 registry.unregisterTexture(textureId)
248 274
249 - analyzeMode = 0 275 +// analyzeMode = 0
250 latestBuffer = nil 276 latestBuffer = nil
251 captureSession = nil 277 captureSession = nil
252 device = nil 278 device = nil
@@ -2,4 +2,5 @@ library mobile_scanner; @@ -2,4 +2,5 @@ library mobile_scanner;
2 2
3 export 'src/mobile_scanner.dart'; 3 export 'src/mobile_scanner.dart';
4 export 'src/mobile_scanner_controller.dart'; 4 export 'src/mobile_scanner_controller.dart';
  5 +export 'src/mobile_scanner_arguments.dart';
5 export 'src/objects/barcode.dart'; 6 export 'src/objects/barcode.dart';
1 -import 'package:flutter/foundation.dart';  
2 import 'package:flutter/material.dart'; 1 import 'package:flutter/material.dart';
3 import 'package:mobile_scanner/mobile_scanner.dart'; 2 import 'package:mobile_scanner/mobile_scanner.dart';
4 3
@@ -29,10 +28,12 @@ class MobileScanner extends StatefulWidget { @@ -29,10 +28,12 @@ class MobileScanner extends StatefulWidget {
29 final BoxFit fit; 28 final BoxFit fit;
30 29
31 /// Create a [MobileScanner] with a [controller], the [controller] must has been initialized. 30 /// Create a [MobileScanner] with a [controller], the [controller] must has been initialized.
32 - const MobileScanner(  
33 - {Key? key, this.onDetect, this.controller, this.fit = BoxFit.cover})  
34 - : assert((controller != null)),  
35 - super(key: key); 31 + const MobileScanner({
  32 + Key? key,
  33 + this.onDetect,
  34 + this.controller,
  35 + this.fit = BoxFit.cover,
  36 + }) : super(key: key);
36 37
37 @override 38 @override
38 State<MobileScanner> createState() => _MobileScannerState(); 39 State<MobileScanner> createState() => _MobileScannerState();
@@ -40,30 +41,26 @@ class MobileScanner extends StatefulWidget { @@ -40,30 +41,26 @@ class MobileScanner extends StatefulWidget {
40 41
41 class _MobileScannerState extends State<MobileScanner> 42 class _MobileScannerState extends State<MobileScanner>
42 with WidgetsBindingObserver { 43 with WidgetsBindingObserver {
43 - bool onScreen = true;  
44 late MobileScannerController controller; 44 late MobileScannerController controller;
45 45
46 @override 46 @override
47 void initState() { 47 void initState() {
48 super.initState(); 48 super.initState();
49 - if (widget.controller == null) {  
50 - controller = MobileScannerController();  
51 - } else {  
52 - controller = widget.controller!;  
53 - } 49 + WidgetsBinding.instance?.addObserver(this);
  50 + controller = widget.controller ?? MobileScannerController();
54 } 51 }
55 52
56 @override 53 @override
57 void didChangeAppLifecycleState(AppLifecycleState state) { 54 void didChangeAppLifecycleState(AppLifecycleState state) {
58 - if (state == AppLifecycleState.resumed) {  
59 - setState(() => onScreen = true);  
60 - } else {  
61 - if (onScreen) { 55 + switch (state) {
  56 + case AppLifecycleState.resumed:
  57 + controller.start();
  58 + break;
  59 + case AppLifecycleState.inactive:
  60 + case AppLifecycleState.paused:
  61 + case AppLifecycleState.detached:
62 controller.stop(); 62 controller.stop();
63 - }  
64 - setState(() {  
65 - onScreen = false;  
66 - }); 63 + break;
67 } 64 }
68 } 65 }
69 66
@@ -76,7 +73,6 @@ class _MobileScannerState extends State<MobileScanner> @@ -76,7 +73,6 @@ class _MobileScannerState extends State<MobileScanner>
76 ); 73 );
77 } else { 74 } else {
78 return LayoutBuilder(builder: (context, BoxConstraints constraints) { 75 return LayoutBuilder(builder: (context, BoxConstraints constraints) {
79 - if (!onScreen) return const Text("Camera Paused.");  
80 return ValueListenableBuilder( 76 return ValueListenableBuilder(
81 valueListenable: controller.args, 77 valueListenable: controller.args,
82 builder: (context, value, child) { 78 builder: (context, value, child) {
@@ -109,13 +105,32 @@ class _MobileScannerState extends State<MobileScanner> @@ -109,13 +105,32 @@ class _MobileScannerState extends State<MobileScanner>
109 ); 105 );
110 } 106 }
111 }); 107 });
  108 + }
112 }); 109 });
113 } 110 }
114 } 111 }
115 112
116 @override 113 @override
  114 + void didUpdateWidget(covariant MobileScanner oldWidget) {
  115 + super.didUpdateWidget(oldWidget);
  116 + if (oldWidget.controller == null) {
  117 + if (widget.controller != null) {
  118 + controller.dispose();
  119 + controller = widget.controller!;
  120 + }
  121 + } else {
  122 + if (widget.controller == null) {
  123 + controller = MobileScannerController();
  124 + } else if (oldWidget.controller != widget.controller) {
  125 + controller = widget.controller!;
  126 + }
  127 + }
  128 + }
  129 +
  130 + @override
117 void dispose() { 131 void dispose() {
118 controller.dispose(); 132 controller.dispose();
  133 + WidgetsBinding.instance?.removeObserver(this);
119 super.dispose(); 134 super.dispose();
120 } 135 }
121 } 136 }
@@ -4,7 +4,6 @@ import 'package:flutter/cupertino.dart'; @@ -4,7 +4,6 @@ import 'package:flutter/cupertino.dart';
4 import 'package:flutter/services.dart'; 4 import 'package:flutter/services.dart';
5 import 'package:mobile_scanner/mobile_scanner.dart'; 5 import 'package:mobile_scanner/mobile_scanner.dart';
6 6
7 -import 'mobile_scanner_arguments.dart';  
8 import 'objects/barcode_utility.dart'; 7 import 'objects/barcode_utility.dart';
9 8
10 /// The facing of a camera. 9 /// The facing of a camera.
@@ -27,7 +26,7 @@ enum TorchState { @@ -27,7 +26,7 @@ enum TorchState {
27 on, 26 on,
28 } 27 }
29 28
30 -enum AnalyzeMode { none, barcode } 29 +// enum AnalyzeMode { none, barcode }
31 30
32 class MobileScannerController { 31 class MobileScannerController {
33 MethodChannel methodChannel = 32 MethodChannel methodChannel =
@@ -62,8 +61,8 @@ class MobileScannerController { @@ -62,8 +61,8 @@ class MobileScannerController {
62 61
63 // Sets analyze mode and barcode stream 62 // Sets analyze mode and barcode stream
64 barcodesController = StreamController.broadcast( 63 barcodesController = StreamController.broadcast(
65 - onListen: () => setAnalyzeMode(AnalyzeMode.barcode.index),  
66 - onCancel: () => setAnalyzeMode(AnalyzeMode.none.index), 64 + // onListen: () => setAnalyzeMode(AnalyzeMode.barcode.index),
  65 + // onCancel: () => setAnalyzeMode(AnalyzeMode.none.index),
67 ); 66 );
68 67
69 start(); 68 start();
@@ -94,12 +93,13 @@ class MobileScannerController { @@ -94,12 +93,13 @@ class MobileScannerController {
94 } 93 }
95 } 94 }
96 95
97 - void setAnalyzeMode(int mode) {  
98 - if (hashCode != _controllerHashcode) {  
99 - return;  
100 - }  
101 - methodChannel.invokeMethod('analyze', mode);  
102 - } 96 + // TODO: Add more analyzers like text analyzer
  97 + // void setAnalyzeMode(int mode) {
  98 + // if (hashCode != _controllerHashcode) {
  99 + // return;
  100 + // }
  101 + // methodChannel.invokeMethod('analyze', mode);
  102 + // }
103 103
104 // List<BarcodeFormats>? formats = _defaultBarcodeFormats, 104 // List<BarcodeFormats>? formats = _defaultBarcodeFormats,
105 /// Start barcode scanning. This will first check if the required permissions 105 /// Start barcode scanning. This will first check if the required permissions
@@ -107,7 +107,7 @@ class MobileScannerController { @@ -107,7 +107,7 @@ class MobileScannerController {
107 Future<void> start() async { 107 Future<void> start() async {
108 ensure('startAsync'); 108 ensure('startAsync');
109 109
110 - setAnalyzeMode(AnalyzeMode.barcode.index); 110 + // setAnalyzeMode(AnalyzeMode.barcode.index);
111 // Check authorization status 111 // Check authorization status
112 MobileScannerState state = 112 MobileScannerState state =
113 MobileScannerState.values[await methodChannel.invokeMethod('state')]; 113 MobileScannerState.values[await methodChannel.invokeMethod('state')];
@@ -132,8 +132,15 @@ class MobileScannerController { @@ -132,8 +132,15 @@ class MobileScannerController {
132 if (torchEnabled != null) arguments['torch'] = torchEnabled; 132 if (torchEnabled != null) arguments['torch'] = torchEnabled;
133 133
134 // Start the camera with arguments 134 // Start the camera with arguments
135 - final Map<String, dynamic>? startResult = await methodChannel  
136 - .invokeMapMethod<String, dynamic>('start', arguments); 135 + Map<String, dynamic>? startResult = {};
  136 + try {
  137 + startResult = await methodChannel.invokeMapMethod<String, dynamic>(
  138 + 'start', arguments);
  139 + } on PlatformException catch (error) {
  140 + debugPrint('${error.code}: ${error.message}');
  141 + // setAnalyzeMode(AnalyzeMode.none.index);
  142 + return;
  143 + }
137 144
138 if (startResult == null) { 145 if (startResult == null) {
139 throw PlatformException(code: 'INITIALIZATION ERROR'); 146 throw PlatformException(code: 'INITIALIZATION ERROR');
@@ -146,17 +153,32 @@ class MobileScannerController { @@ -146,17 +153,32 @@ class MobileScannerController {
146 hasTorch: hasTorch); 153 hasTorch: hasTorch);
147 } 154 }
148 155
149 - Future<void> stop() async => await methodChannel.invokeMethod('stop'); 156 + Future<void> stop() async {
  157 + try {
  158 + await methodChannel.invokeMethod('stop');
  159 + } on PlatformException catch (error) {
  160 + debugPrint('${error.code}: ${error.message}');
  161 + }
  162 + }
150 163
151 /// Switches the torch on or off. 164 /// Switches the torch on or off.
152 /// 165 ///
153 /// Only works if torch is available. 166 /// Only works if torch is available.
154 - void toggleTorch() { 167 + Future<void> toggleTorch() async {
155 ensure('toggleTorch'); 168 ensure('toggleTorch');
156 - if (!hasTorch) return; 169 + if (!hasTorch) {
  170 + debugPrint('Device has no torch/flash.');
  171 + return;
  172 + }
  173 +
157 TorchState state = 174 TorchState state =
158 torchState.value == TorchState.off ? TorchState.on : TorchState.off; 175 torchState.value == TorchState.off ? TorchState.on : TorchState.off;
159 - methodChannel.invokeMethod('torch', state.index); 176 +
  177 + try {
  178 + await methodChannel.invokeMethod('torch', state.index);
  179 + } on PlatformException catch (error) {
  180 + debugPrint('${error.code}: ${error.message}');
  181 + }
160 } 182 }
161 183
162 /// Switches the torch on or off. 184 /// Switches the torch on or off.
@@ -164,10 +186,16 @@ class MobileScannerController { @@ -164,10 +186,16 @@ class MobileScannerController {
164 /// Only works if torch is available. 186 /// Only works if torch is available.
165 Future<void> switchCamera() async { 187 Future<void> switchCamera() async {
166 ensure('switchCamera'); 188 ensure('switchCamera');
167 - await stop(); 189 + try {
  190 + await methodChannel.invokeMethod('stop');
  191 + } on PlatformException catch (error) {
  192 + debugPrint(
  193 + '${error.code}: camera is stopped! Please start before switching camera.');
  194 + return;
  195 + }
168 facing = 196 facing =
169 facing == CameraFacing.back ? CameraFacing.front : CameraFacing.back; 197 facing == CameraFacing.back ? CameraFacing.front : CameraFacing.back;
170 - start(); 198 + await start();
171 } 199 }
172 200
173 /// Disposes the controller and closes all listeners. 201 /// Disposes the controller and closes all listeners.
@@ -25,7 +25,7 @@ class Barcode { @@ -25,7 +25,7 @@ class Barcode {
25 /// It's only available when the barcode is encoded in the UTF-8 format, and for non-UTF8 ones use [rawBytes] instead. 25 /// It's only available when the barcode is encoded in the UTF-8 format, and for non-UTF8 ones use [rawBytes] instead.
26 /// 26 ///
27 /// Returns null if the raw value can not be determined. 27 /// Returns null if the raw value can not be determined.
28 - final String rawValue; 28 + final String? rawValue;
29 29
30 /// Returns format type of the barcode value. 30 /// Returns format type of the barcode value.
31 /// 31 ///
@@ -63,7 +63,21 @@ class Barcode { @@ -63,7 +63,21 @@ class Barcode {
63 /// Gets parsed WiFi AP details. 63 /// Gets parsed WiFi AP details.
64 final WiFi? wifi; 64 final WiFi? wifi;
65 65
66 - 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}); 66 + Barcode(
  67 + {this.corners,
  68 + this.format = BarcodeFormat.ean13,
  69 + this.rawBytes,
  70 + this.type = BarcodeType.text,
  71 + this.calendarEvent,
  72 + this.contactInfo,
  73 + this.driverLicense,
  74 + this.email,
  75 + this.geoPoint,
  76 + this.phone,
  77 + this.sms,
  78 + this.url,
  79 + this.wifi,
  80 + required this.rawValue});
67 81
68 /// Create a [Barcode] from native data. 82 /// Create a [Barcode] from native data.
69 Barcode.fromNative(Map<dynamic, dynamic> data) 83 Barcode.fromNative(Map<dynamic, dynamic> data)
@@ -151,22 +165,22 @@ class ContactInfo { @@ -151,22 +165,22 @@ class ContactInfo {
151 /// Gets contact person's organization. 165 /// Gets contact person's organization.
152 /// 166 ///
153 /// Returns null if not available. 167 /// Returns null if not available.
154 - final String organization; 168 + final String? organization;
155 169
156 /// Gets contact person's phones. 170 /// Gets contact person's phones.
157 /// 171 ///
158 /// Returns an empty list if nothing found. 172 /// Returns an empty list if nothing found.
159 - final List<Phone> phones; 173 + final List<Phone>? phones;
160 174
161 /// Gets contact person's title. 175 /// Gets contact person's title.
162 /// 176 ///
163 /// Returns null if not available. 177 /// Returns null if not available.
164 - final String title; 178 + final String? title;
165 179
166 /// Gets contact person's urls. 180 /// Gets contact person's urls.
167 /// 181 ///
168 /// Returns an empty list if nothing found. 182 /// Returns an empty list if nothing found.
169 - final List<String> urls; 183 + final List<String>? urls;
170 184
171 /// Create a [ContactInfo] from native data. 185 /// Create a [ContactInfo] from native data.
172 ContactInfo.fromNative(Map<dynamic, dynamic> data) 186 ContactInfo.fromNative(Map<dynamic, dynamic> data)
@@ -188,7 +202,9 @@ class Address { @@ -188,7 +202,9 @@ class Address {
188 final List<String> addressLines; 202 final List<String> addressLines;
189 203
190 /// Gets type of the address. 204 /// Gets type of the address.
191 - final AddressType type; 205 + ///
  206 + /// Returns null if not available.
  207 + final AddressType? type;
192 208
193 /// Create a [Address] from native data. 209 /// Create a [Address] from native data.
194 Address.fromNative(Map<dynamic, dynamic> data) 210 Address.fromNative(Map<dynamic, dynamic> data)
@@ -201,37 +217,37 @@ class PersonName { @@ -201,37 +217,37 @@ class PersonName {
201 /// Gets first name. 217 /// Gets first name.
202 /// 218 ///
203 /// Returns null if not available. 219 /// Returns null if not available.
204 - final String first; 220 + final String? first;
205 221
206 /// Gets middle name. 222 /// Gets middle name.
207 /// 223 ///
208 /// Returns null if not available. 224 /// Returns null if not available.
209 - final String middle; 225 + final String? middle;
210 226
211 /// Gets last name. 227 /// Gets last name.
212 /// 228 ///
213 /// Returns null if not available. 229 /// Returns null if not available.
214 - final String last; 230 + final String? last;
215 231
216 /// Gets prefix of the name. 232 /// Gets prefix of the name.
217 /// 233 ///
218 /// Returns null if not available. 234 /// Returns null if not available.
219 - final String prefix; 235 + final String? prefix;
220 236
221 /// Gets suffix of the person's name. 237 /// Gets suffix of the person's name.
222 /// 238 ///
223 /// Returns null if not available. 239 /// Returns null if not available.
224 - final String suffix; 240 + final String? suffix;
225 241
226 /// Gets the properly formatted name. 242 /// Gets the properly formatted name.
227 /// 243 ///
228 /// Returns null if not available. 244 /// Returns null if not available.
229 - final String formattedName; 245 + final String? formattedName;
230 246
231 /// Designates a text string to be set as the kana name in the phonebook. Used for Japanese contacts. 247 /// Designates a text string to be set as the kana name in the phonebook. Used for Japanese contacts.
232 /// 248 ///
233 /// Returns null if not available. 249 /// Returns null if not available.
234 - final String pronunciation; 250 + final String? pronunciation;
235 251
236 /// Create a [PersonName] from native data. 252 /// Create a [PersonName] from native data.
237 PersonName.fromNative(Map<dynamic, dynamic> data) 253 PersonName.fromNative(Map<dynamic, dynamic> data)
@@ -249,74 +265,74 @@ class DriverLicense { @@ -249,74 +265,74 @@ class DriverLicense {
249 /// Gets city of holder's address. 265 /// Gets city of holder's address.
250 /// 266 ///
251 /// Returns null if not available. 267 /// Returns null if not available.
252 - final String addressCity; 268 + final String? addressCity;
253 269
254 /// Gets state of holder's address. 270 /// Gets state of holder's address.
255 /// 271 ///
256 /// Returns null if not available. 272 /// Returns null if not available.
257 - final String addressState; 273 + final String? addressState;
258 274
259 /// Gets holder's street address. 275 /// Gets holder's street address.
260 /// 276 ///
261 /// Returns null if not available. 277 /// Returns null if not available.
262 - final String addressStreet; 278 + final String? addressStreet;
263 279
264 /// Gets postal code of holder's address. 280 /// Gets postal code of holder's address.
265 /// 281 ///
266 /// Returns null if not available. 282 /// Returns null if not available.
267 - final String addressZip; 283 + final String? addressZip;
268 284
269 /// Gets birth date of the holder. 285 /// Gets birth date of the holder.
270 /// 286 ///
271 /// Returns null if not available. 287 /// Returns null if not available.
272 - final String birthDate; 288 + final String? birthDate;
273 289
274 /// Gets "DL" for driver licenses, "ID" for ID cards. 290 /// Gets "DL" for driver licenses, "ID" for ID cards.
275 /// 291 ///
276 /// Returns null if not available. 292 /// Returns null if not available.
277 - final String documentType; 293 + final String? documentType;
278 294
279 /// Gets expiry date of the license. 295 /// Gets expiry date of the license.
280 /// 296 ///
281 /// Returns null if not available. 297 /// Returns null if not available.
282 - final String expiryDate; 298 + final String? expiryDate;
283 299
284 /// Gets holder's first name. 300 /// Gets holder's first name.
285 /// 301 ///
286 /// Returns null if not available. 302 /// Returns null if not available.
287 - final String firstName; 303 + final String? firstName;
288 304
289 /// Gets holder's gender. 1 - male, 2 - female. 305 /// Gets holder's gender. 1 - male, 2 - female.
290 /// 306 ///
291 /// Returns null if not available. 307 /// Returns null if not available.
292 - final String gender; 308 + final String? gender;
293 309
294 /// Gets issue date of the license. 310 /// Gets issue date of the license.
295 /// 311 ///
296 /// The date format depends on the issuing country. MMDDYYYY for the US, YYYYMMDD for Canada. 312 /// The date format depends on the issuing country. MMDDYYYY for the US, YYYYMMDD for Canada.
297 /// 313 ///
298 /// Returns null if not available. 314 /// Returns null if not available.
299 - final String issueDate; 315 + final String? issueDate;
300 316
301 /// Gets the three-letter country code in which DL/ID was issued. 317 /// Gets the three-letter country code in which DL/ID was issued.
302 /// 318 ///
303 /// Returns null if not available. 319 /// Returns null if not available.
304 - final String issuingCountry; 320 + final String? issuingCountry;
305 321
306 /// Gets holder's last name. 322 /// Gets holder's last name.
307 /// 323 ///
308 /// Returns null if not available. 324 /// Returns null if not available.
309 - final String lastName; 325 + final String? lastName;
310 326
311 /// Gets driver license ID number. 327 /// Gets driver license ID number.
312 /// 328 ///
313 /// Returns null if not available. 329 /// Returns null if not available.
314 - final String licenseNumber; 330 + final String? licenseNumber;
315 331
316 /// Gets holder's middle name. 332 /// Gets holder's middle name.
317 /// 333 ///
318 /// Returns null if not available. 334 /// Returns null if not available.
319 - final String middleName; 335 + final String? middleName;
320 336
321 /// Create a [DriverLicense] from native data. 337 /// Create a [DriverLicense] from native data.
322 DriverLicense.fromNative(Map<dynamic, dynamic> data) 338 DriverLicense.fromNative(Map<dynamic, dynamic> data)
@@ -341,22 +357,23 @@ class Email { @@ -341,22 +357,23 @@ class Email {
341 /// Gets email's address. 357 /// Gets email's address.
342 /// 358 ///
343 /// Returns null if not available. 359 /// Returns null if not available.
344 - final String address; 360 + final String? address;
345 361
346 /// Gets email's body. 362 /// Gets email's body.
347 /// 363 ///
348 /// Returns null if not available. 364 /// Returns null if not available.
349 - final String body; 365 + final String? body;
350 366
351 /// Gets email's subject. 367 /// Gets email's subject.
352 /// 368 ///
353 /// Returns null if not available. 369 /// Returns null if not available.
354 - final String subject; 370 + final String? subject;
355 371
356 /// Gets type of the email. 372 /// Gets type of the email.
357 /// 373 ///
358 /// See also [EmailType]. 374 /// See also [EmailType].
359 - final EmailType type; 375 + /// Returns null if not available.
  376 + final EmailType? type;
360 377
361 /// Create a [Email] from native data. 378 /// Create a [Email] from native data.
362 Email.fromNative(Map<dynamic, dynamic> data) 379 Email.fromNative(Map<dynamic, dynamic> data)
@@ -369,10 +386,10 @@ class Email { @@ -369,10 +386,10 @@ class Email {
369 /// GPS coordinates from a 'GEO:' or similar QRCode type. 386 /// GPS coordinates from a 'GEO:' or similar QRCode type.
370 class GeoPoint { 387 class GeoPoint {
371 /// Gets the latitude. 388 /// Gets the latitude.
372 - final double latitude; 389 + final double? latitude;
373 390
374 /// Gets the longitude. 391 /// Gets the longitude.
375 - final double longitude; 392 + final double? longitude;
376 393
377 /// Create a [GeoPoint] from native data. 394 /// Create a [GeoPoint] from native data.
378 GeoPoint.fromNative(Map<dynamic, dynamic> data) 395 GeoPoint.fromNative(Map<dynamic, dynamic> data)
@@ -385,12 +402,13 @@ class Phone { @@ -385,12 +402,13 @@ class Phone {
385 /// Gets phone number. 402 /// Gets phone number.
386 /// 403 ///
387 /// Returns null if not available. 404 /// Returns null if not available.
388 - final String number; 405 + final String? number;
389 406
390 /// Gets type of the phone number. 407 /// Gets type of the phone number.
391 /// 408 ///
392 /// See also [PhoneType]. 409 /// See also [PhoneType].
393 - final PhoneType type; 410 + /// Returns null if not available.
  411 + final PhoneType? type;
394 412
395 /// Create a [Phone] from native data. 413 /// Create a [Phone] from native data.
396 Phone.fromNative(Map<dynamic, dynamic> data) 414 Phone.fromNative(Map<dynamic, dynamic> data)
@@ -403,12 +421,12 @@ class SMS { @@ -403,12 +421,12 @@ class SMS {
403 /// Gets the message content of the sms. 421 /// Gets the message content of the sms.
404 /// 422 ///
405 /// Returns null if not available. 423 /// Returns null if not available.
406 - final String message; 424 + final String? message;
407 425
408 /// Gets the phone number of the sms. 426 /// Gets the phone number of the sms.
409 /// 427 ///
410 /// Returns null if not available. 428 /// Returns null if not available.
411 - final String phoneNumber; 429 + final String? phoneNumber;
412 430
413 /// Create a [SMS] from native data. 431 /// Create a [SMS] from native data.
414 SMS.fromNative(Map<dynamic, dynamic> data) 432 SMS.fromNative(Map<dynamic, dynamic> data)
@@ -421,12 +439,12 @@ class UrlBookmark { @@ -421,12 +439,12 @@ class UrlBookmark {
421 /// Gets the title of the bookmark. 439 /// Gets the title of the bookmark.
422 /// 440 ///
423 /// Returns null if not available. 441 /// Returns null if not available.
424 - final String title; 442 + final String? title;
425 443
426 /// Gets the url of the bookmark. 444 /// Gets the url of the bookmark.
427 /// 445 ///
428 /// Returns null if not available. 446 /// Returns null if not available.
429 - final String url; 447 + final String? url;
430 448
431 /// Create a [UrlBookmark] from native data. 449 /// Create a [UrlBookmark] from native data.
432 UrlBookmark.fromNative(Map<dynamic, dynamic> data) 450 UrlBookmark.fromNative(Map<dynamic, dynamic> data)
@@ -444,12 +462,12 @@ class WiFi { @@ -444,12 +462,12 @@ class WiFi {
444 /// Gets the ssid of the WIFI. 462 /// Gets the ssid of the WIFI.
445 /// 463 ///
446 /// Returns null if not available. 464 /// Returns null if not available.
447 - final String ssid; 465 + final String? ssid;
448 466
449 /// Gets the password of the WIFI. 467 /// Gets the password of the WIFI.
450 /// 468 ///
451 /// Returns null if not available. 469 /// Returns null if not available.
452 - final String password; 470 + final String? password;
453 471
454 /// Create a [WiFi] from native data. 472 /// Create a [WiFi] from native data.
455 WiFi.fromNative(Map<dynamic, dynamic> data) 473 WiFi.fromNative(Map<dynamic, dynamic> data)
@@ -22,7 +22,7 @@ public class MobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHandler, @@ -22,7 +22,7 @@ public class MobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHandler,
22 var latestBuffer: CVImageBuffer! 22 var latestBuffer: CVImageBuffer!
23 23
24 24
25 - var analyzeMode: Int = 0 25 +// var analyzeMode: Int = 0
26 var analyzing: Bool = false 26 var analyzing: Bool = false
27 var position = AVCaptureDevice.Position.back 27 var position = AVCaptureDevice.Position.back
28 28
@@ -52,9 +52,9 @@ public class MobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHandler, @@ -52,9 +52,9 @@ public class MobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHandler,
52 case "start": 52 case "start":
53 start(call, result) 53 start(call, result)
54 case "torch": 54 case "torch":
55 - switchTorch(call, result)  
56 - case "analyze":  
57 - switchAnalyzeMode(call, result) 55 + toggleTorch(call, result)
  56 +// case "analyze":
  57 +// switchAnalyzeMode(call, result)
58 case "stop": 58 case "stop":
59 stop(result) 59 stop(result)
60 default: 60 default:
@@ -92,14 +92,14 @@ public class MobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHandler, @@ -92,14 +92,14 @@ public class MobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHandler,
92 latestBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) 92 latestBuffer = CMSampleBufferGetImageBuffer(sampleBuffer)
93 registry.textureFrameAvailable(textureId) 93 registry.textureFrameAvailable(textureId)
94 94
95 - switch analyzeMode {  
96 - case 1: // barcode 95 +// switch analyzeMode {
  96 +// case 1: // barcode
97 97
98 // Limit the analyzer because the texture output will freeze otherwise 98 // Limit the analyzer because the texture output will freeze otherwise
99 if i / 10 == 1 { 99 if i / 10 == 1 {
100 i = 0 100 i = 0
101 } else { 101 } else {
102 - break 102 + return
103 } 103 }
104 let imageRequestHandler = VNImageRequestHandler( 104 let imageRequestHandler = VNImageRequestHandler(
105 cvPixelBuffer: latestBuffer, 105 cvPixelBuffer: latestBuffer,
@@ -129,9 +129,9 @@ public class MobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHandler, @@ -129,9 +129,9 @@ public class MobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHandler,
129 print(error) 129 print(error)
130 } 130 }
131 131
132 - default: // none  
133 - break  
134 - } 132 +// default: // none
  133 +// break
  134 +// }
135 } 135 }
136 136
137 func checkPermission(_ call: FlutterMethodCall, _ result: @escaping FlutterResult) { 137 func checkPermission(_ call: FlutterMethodCall, _ result: @escaping FlutterResult) {
@@ -159,6 +159,13 @@ public class MobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHandler, @@ -159,6 +159,13 @@ public class MobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHandler,
159 } 159 }
160 160
161 func start(_ call: FlutterMethodCall, _ result: @escaping FlutterResult) { 161 func start(_ call: FlutterMethodCall, _ result: @escaping FlutterResult) {
  162 + if (device != nil) {
  163 + result(FlutterError(code: "MobileScanner",
  164 + message: "Called start() while already started!",
  165 + details: nil))
  166 + return
  167 + }
  168 +
162 textureId = registry.register(self) 169 textureId = registry.register(self)
163 captureSession = AVCaptureSession() 170 captureSession = AVCaptureSession()
164 171
@@ -178,6 +185,13 @@ public class MobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHandler, @@ -178,6 +185,13 @@ public class MobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHandler,
178 device = AVCaptureDevice.devices(for: .video).filter({$0.position == position}).first 185 device = AVCaptureDevice.devices(for: .video).filter({$0.position == position}).first
179 } 186 }
180 187
  188 + if (device == nil) {
  189 + result(FlutterError(code: "MobileScanner",
  190 + message: "No camera found or failed to open camera!",
  191 + details: nil))
  192 + return
  193 + }
  194 +
181 // Enable the torch if parameter is set and torch is available 195 // Enable the torch if parameter is set and torch is available
182 if (device.hasTorch) { 196 if (device.hasTorch) {
183 do { 197 do {
@@ -222,7 +236,13 @@ public class MobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHandler, @@ -222,7 +236,13 @@ public class MobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHandler,
222 result(answer) 236 result(answer)
223 } 237 }
224 238
225 - func switchTorch(_ call: FlutterMethodCall, _ result: @escaping FlutterResult) { 239 + func toggleTorch(_ call: FlutterMethodCall, _ result: @escaping FlutterResult) {
  240 + if (device == nil) {
  241 + result(FlutterError(code: "MobileScanner",
  242 + message: "Called toggleTorch() while stopped!",
  243 + details: nil))
  244 + return
  245 + }
226 do { 246 do {
227 try device.lockForConfiguration() 247 try device.lockForConfiguration()
228 device.torchMode = call.arguments as! Int == 1 ? .on : .off 248 device.torchMode = call.arguments as! Int == 1 ? .on : .off
@@ -233,12 +253,18 @@ public class MobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHandler, @@ -233,12 +253,18 @@ public class MobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHandler,
233 } 253 }
234 } 254 }
235 255
236 - func switchAnalyzeMode(_ call: FlutterMethodCall, _ result: @escaping FlutterResult) {  
237 - analyzeMode = call.arguments as! Int  
238 - result(nil)  
239 - } 256 +// func switchAnalyzeMode(_ call: FlutterMethodCall, _ result: @escaping FlutterResult) {
  257 +// analyzeMode = call.arguments as! Int
  258 +// result(nil)
  259 +// }
240 260
241 func stop(_ result: FlutterResult) { 261 func stop(_ result: FlutterResult) {
  262 + if (device == nil) {
  263 + result(FlutterError(code: "MobileScanner",
  264 + message: "Called stop() while already stopped!",
  265 + details: nil))
  266 + return
  267 + }
242 captureSession.stopRunning() 268 captureSession.stopRunning()
243 for input in captureSession.inputs { 269 for input in captureSession.inputs {
244 captureSession.removeInput(input) 270 captureSession.removeInput(input)
@@ -249,7 +275,7 @@ public class MobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHandler, @@ -249,7 +275,7 @@ public class MobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHandler,
249 device.removeObserver(self, forKeyPath: #keyPath(AVCaptureDevice.torchMode)) 275 device.removeObserver(self, forKeyPath: #keyPath(AVCaptureDevice.torchMode))
250 registry.unregisterTexture(textureId) 276 registry.unregisterTexture(textureId)
251 277
252 - analyzeMode = 0 278 +// analyzeMode = 0
253 latestBuffer = nil 279 latestBuffer = nil
254 captureSession = nil 280 captureSession = nil
255 device = nil 281 device = nil
1 name: mobile_scanner 1 name: mobile_scanner
2 -description: A universal scanner for Flutter based on MLKit. Uses CameraX on Android and AVFoundation on iOS.  
3 -version: 0.1.0 2 +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.
  3 +version: 0.1.2
4 repository: https://github.com/juliansteenbakker/mobile_scanner 4 repository: https://github.com/juliansteenbakker/mobile_scanner
5 5
6 environment: 6 environment:
7 - sdk: ">=2.16.0 <3.0.0"  
8 - flutter: ">=2.5.0" 7 + sdk: ">=2.12.0 <3.0.0"
  8 + flutter: ">=2.2.0"
9 9
10 dependencies: 10 dependencies:
11 js: ^0.6.4 11 js: ^0.6.4