Julian Steenbakker

Merge branch 'master' into image-picker

# Conflicts:
#	example/lib/main.dart
  1 +## 0.1.3
  2 +* Fixed crash after asking permission. [#29](https://github.com/juliansteenbakker/mobile_scanner/issues/29)
  3 +* Upgraded cameraX from 1.1.0-beta01 to 1.1.0-beta02
  4 +
  5 +## 0.1.2
  6 +* MobileScannerArguments is now exported. [#7](https://github.com/juliansteenbakker/mobile_scanner/issues/7)
  7 +
  8 +Bugfixes:
  9 +* Fixed application crashing when stop() or start() is called multiple times. [#5](https://github.com/juliansteenbakker/mobile_scanner/issues/5)
  10 +* Fixes controller not being disposed correctly. [#23](https://github.com/juliansteenbakker/mobile_scanner/issues/23)
  11 +* Catch error when no camera is found. [#19](https://github.com/juliansteenbakker/mobile_scanner/issues/19)
  12 +
1 ## 0.1.1 13 ## 0.1.1
2 mobile_scanner is now compatible with sdk >= 2.12 and flutter >= 2.2.0 14 mobile_scanner is now compatible with sdk >= 2.12 and flutter >= 2.2.0
3 15
1 -include: package:flutter_lints/flutter.yaml  
  1 +include: package:flutter_lints/flutter.yaml
  2 +
  3 +linter:
  4 + rules:
  5 + unawaited_futures: true
@@ -9,7 +9,7 @@ buildscript { @@ -9,7 +9,7 @@ buildscript {
9 } 9 }
10 10
11 dependencies { 11 dependencies {
12 - classpath 'com.android.tools.build:gradle:7.1.1' 12 + classpath 'com.android.tools.build:gradle:7.1.2'
13 } 13 }
14 } 14 }
15 15
@@ -47,8 +47,8 @@ android { @@ -47,8 +47,8 @@ android {
47 dependencies { 47 dependencies {
48 implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 48 implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
49 implementation 'com.google.mlkit:barcode-scanning:17.0.2' 49 implementation 'com.google.mlkit:barcode-scanning:17.0.2'
50 - implementation "androidx.camera:camera-camera2:1.1.0-beta01"  
51 - implementation 'androidx.camera:camera-lifecycle:1.1.0-beta01' 50 + implementation "androidx.camera:camera-camera2:1.1.0-beta02"
  51 + implementation 'androidx.camera:camera-lifecycle:1.1.0-beta02'
52 52
53 // // The following line is optional, as the core library is included indirectly by camera-camera2 53 // // The following line is optional, as the core library is included indirectly by camera-camera2
54 // implementation "androidx.camera:camera-core:1.1.0-alpha11" 54 // implementation "androidx.camera:camera-core:1.1.0-alpha11"
@@ -41,8 +41,8 @@ class MobileScanner(private val activity: Activity, private val textureRegistry: @@ -41,8 +41,8 @@ class MobileScanner(private val activity: Activity, private val textureRegistry:
41 private var camera: Camera? = null 41 private var camera: Camera? = null
42 private var textureEntry: TextureRegistry.SurfaceTextureEntry? = null 42 private var textureEntry: TextureRegistry.SurfaceTextureEntry? = null
43 43
44 - @AnalyzeMode  
45 - private var analyzeMode: Int = AnalyzeMode.NONE 44 +// @AnalyzeMode
  45 +// private var analyzeMode: Int = AnalyzeMode.NONE
46 46
47 @ExperimentalGetImage 47 @ExperimentalGetImage
48 override fun onMethodCall(@NonNull call: MethodCall, @NonNull result: MethodChannel.Result) { 48 override fun onMethodCall(@NonNull call: MethodCall, @NonNull result: MethodChannel.Result) {
@@ -50,8 +50,8 @@ class MobileScanner(private val activity: Activity, private val textureRegistry: @@ -50,8 +50,8 @@ class MobileScanner(private val activity: Activity, private val textureRegistry:
50 "state" -> checkPermission(result) 50 "state" -> checkPermission(result)
51 "request" -> requestPermission(result) 51 "request" -> requestPermission(result)
52 "start" -> start(call, result) 52 "start" -> start(call, result)
53 - "torch" -> switchTorch(call, result)  
54 - "analyze" -> switchAnalyzeMode(call, result) 53 + "torch" -> toggleTorch(call, result)
  54 +// "analyze" -> switchAnalyzeMode(call, result)
55 "stop" -> stop(result) 55 "stop" -> stop(result)
56 "analyzeImage" -> analyzeImage(call, result) 56 "analyzeImage" -> analyzeImage(call, result)
57 else -> result.notImplemented() 57 else -> result.notImplemented()
@@ -96,8 +96,8 @@ class MobileScanner(private val activity: Activity, private val textureRegistry: @@ -96,8 +96,8 @@ class MobileScanner(private val activity: Activity, private val textureRegistry:
96 96
97 @ExperimentalGetImage 97 @ExperimentalGetImage
98 val analyzer = ImageAnalysis.Analyzer { imageProxy -> // YUV_420_888 format 98 val analyzer = ImageAnalysis.Analyzer { imageProxy -> // YUV_420_888 format
99 - when (analyzeMode) {  
100 - AnalyzeMode.BARCODE -> { 99 +// when (analyzeMode) {
  100 +// AnalyzeMode.BARCODE -> {
101 val mediaImage = imageProxy.image ?: return@Analyzer 101 val mediaImage = imageProxy.image ?: return@Analyzer
102 val inputImage = InputImage.fromMediaImage(mediaImage, imageProxy.imageInfo.rotationDegrees) 102 val inputImage = InputImage.fromMediaImage(mediaImage, imageProxy.imageInfo.rotationDegrees)
103 103
@@ -110,9 +110,9 @@ class MobileScanner(private val activity: Activity, private val textureRegistry: @@ -110,9 +110,9 @@ class MobileScanner(private val activity: Activity, private val textureRegistry:
110 } 110 }
111 .addOnFailureListener { e -> Log.e(TAG, e.message, e) } 111 .addOnFailureListener { e -> Log.e(TAG, e.message, e) }
112 .addOnCompleteListener { imageProxy.close() } 112 .addOnCompleteListener { imageProxy.close() }
113 - }  
114 - else -> imageProxy.close()  
115 - } 113 +// }
  114 +// else -> imageProxy.close()
  115 +// }
116 } 116 }
117 117
118 118
@@ -120,6 +120,10 @@ class MobileScanner(private val activity: Activity, private val textureRegistry: @@ -120,6 +120,10 @@ class MobileScanner(private val activity: Activity, private val textureRegistry:
120 120
121 @ExperimentalGetImage 121 @ExperimentalGetImage
122 private fun start(call: MethodCall, result: MethodChannel.Result) { 122 private fun start(call: MethodCall, result: MethodChannel.Result) {
  123 + if (camera != null) {
  124 + result.error(TAG, "Called start() while already started!", null)
  125 + return
  126 + }
123 127
124 val facing: Int = call.argument<Int>("facing") ?: 0 128 val facing: Int = call.argument<Int>("facing") ?: 0
125 val ratio: Int? = call.argument<Int>("ratio") 129 val ratio: Int? = call.argument<Int>("ratio")
@@ -136,6 +140,7 @@ class MobileScanner(private val activity: Activity, private val textureRegistry: @@ -136,6 +140,7 @@ class MobileScanner(private val activity: Activity, private val textureRegistry:
136 140
137 future.addListener({ 141 future.addListener({
138 cameraProvider = future.get() 142 cameraProvider = future.get()
  143 + cameraProvider!!.unbindAll()
139 textureEntry = textureRegistry.createSurfaceTexture() 144 textureEntry = textureRegistry.createSurfaceTexture()
140 145
141 // Preview 146 // Preview
@@ -190,16 +195,19 @@ class MobileScanner(private val activity: Activity, private val textureRegistry: @@ -190,16 +195,19 @@ class MobileScanner(private val activity: Activity, private val textureRegistry:
190 }, executor) 195 }, executor)
191 } 196 }
192 197
193 - private fun switchTorch(call: MethodCall, result: MethodChannel.Result) {  
194 - val state = call.arguments == 1  
195 - camera!!.cameraControl.enableTorch(state) 198 + private fun toggleTorch(call: MethodCall, result: MethodChannel.Result) {
  199 + if (camera == null) {
  200 + result.error(TAG,"Called toggleTorch() while stopped!", null)
  201 + return
  202 + }
  203 + camera!!.cameraControl.enableTorch(call.arguments == 1)
196 result.success(null) 204 result.success(null)
197 } 205 }
198 206
199 - private fun switchAnalyzeMode(call: MethodCall, result: MethodChannel.Result) {  
200 - analyzeMode = call.arguments as Int  
201 - result.success(null)  
202 - } 207 +// private fun switchAnalyzeMode(call: MethodCall, result: MethodChannel.Result) {
  208 +// analyzeMode = call.arguments as Int
  209 +// result.success(null)
  210 +// }
203 211
204 private fun analyzeImage(call: MethodCall, result: MethodChannel.Result) { 212 private fun analyzeImage(call: MethodCall, result: MethodChannel.Result) {
205 val uri = Uri.fromFile( File(call.arguments.toString())) 213 val uri = Uri.fromFile( File(call.arguments.toString()))
@@ -219,12 +227,17 @@ class MobileScanner(private val activity: Activity, private val textureRegistry: @@ -219,12 +227,17 @@ class MobileScanner(private val activity: Activity, private val textureRegistry:
219 } 227 }
220 228
221 private fun stop(result: MethodChannel.Result) { 229 private fun stop(result: MethodChannel.Result) {
  230 + if (camera == null) {
  231 + result.error(TAG,"Called stop() while already stopped!", null)
  232 + return
  233 + }
  234 +
222 val owner = activity as LifecycleOwner 235 val owner = activity as LifecycleOwner
223 camera!!.cameraInfo.torchState.removeObservers(owner) 236 camera!!.cameraInfo.torchState.removeObservers(owner)
224 cameraProvider!!.unbindAll() 237 cameraProvider!!.unbindAll()
225 textureEntry!!.release() 238 textureEntry!!.release()
226 239
227 - analyzeMode = AnalyzeMode.NONE 240 +// analyzeMode = AnalyzeMode.NONE
228 camera = null 241 camera = null
229 textureEntry = null 242 textureEntry = null
230 cameraProvider = null 243 cameraProvider = null
@@ -6,7 +6,7 @@ buildscript { @@ -6,7 +6,7 @@ buildscript {
6 } 6 }
7 7
8 dependencies { 8 dependencies {
9 - classpath 'com.android.tools.build:gradle:7.1.1' 9 + classpath 'com.android.tools.build:gradle:7.1.2'
10 classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 10 classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
11 } 11 }
12 } 12 }
  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 + bool isStarted = true;
  23 +
  24 + @override
  25 + 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(
  53 + crossAxisAlignment: CrossAxisAlignment.center,
  54 + mainAxisAlignment: MainAxisAlignment.spaceEvenly,
  55 + 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 + }
  69 + },
  70 + ),
  71 + iconSize: 32.0,
  72 + onPressed: () => controller.toggleTorch(),
  73 + ),
  74 + IconButton(
  75 + color: Colors.white,
  76 + icon: isStarted
  77 + ? const Icon(Icons.stop)
  78 + : const Icon(Icons.play_arrow),
  79 + iconSize: 32.0,
  80 + onPressed: () => setState(() {
  81 + isStarted
  82 + ? controller.stop()
  83 + : controller.start();
  84 + isStarted = !isStarted;
  85 + })),
  86 + Center(
  87 + child: SizedBox(
  88 + width: MediaQuery.of(context).size.width - 160,
  89 + height: 50,
  90 + child: FittedBox(
  91 + child: Text(
  92 + barcode ?? 'Scan something!',
  93 + overflow: TextOverflow.fade,
  94 + style: Theme.of(context)
  95 + .textTheme
  96 + .headline4!
  97 + .copyWith(color: Colors.white),
  98 + ),
  99 + ),
  100 + ),
  101 + ),
  102 + IconButton(
  103 + color: Colors.white,
  104 + icon: ValueListenableBuilder(
  105 + valueListenable: controller.cameraFacingState,
  106 + builder: (context, state, child) {
  107 + switch (state as CameraFacing) {
  108 + case CameraFacing.front:
  109 + return const Icon(Icons.camera_front);
  110 + case CameraFacing.back:
  111 + return const Icon(Icons.camera_rear);
  112 + }
  113 + },
  114 + ),
  115 + iconSize: 32.0,
  116 + onPressed: () => controller.switchCamera(),
  117 + ),
  118 + ],
  119 + ),
  120 + ),
  121 + ),
  122 + ],
  123 + );
  124 + }),
  125 + ),
  126 + );
  127 + }
  128 +}
  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:image_picker/image_picker.dart';  
3 -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';
4 4
5 -void main() {  
6 - runApp(const AnalyzeView());  
7 -} 5 +void main() => runApp(const MaterialApp(home: MyHome()));
8 6
9 -class AnalyzeView extends StatefulWidget {  
10 - const AnalyzeView({Key? key}) : super(key: key);  
11 -  
12 - @override  
13 - _AnalyzeViewState createState() => _AnalyzeViewState();  
14 -}  
15 -  
16 -class _AnalyzeViewState extends State<AnalyzeView>  
17 - with SingleTickerProviderStateMixin {  
18 - String? barcode;  
19 -  
20 - MobileScannerController controller = MobileScannerController(  
21 - torchEnabled: true,  
22 - facing: CameraFacing.front,  
23 - ); 7 +class MyHome extends StatelessWidget {
  8 + const MyHome({Key? key}) : super(key: key);
24 9
25 @override 10 @override
26 Widget build(BuildContext context) { 11 Widget build(BuildContext context) {
27 - return MaterialApp(  
28 - home: Scaffold(  
29 - backgroundColor: Colors.black,  
30 - body: Builder(builder: (context) {  
31 - return Stack(  
32 - children: [  
33 - MobileScanner(  
34 - controller: controller,  
35 - fit: BoxFit.contain,  
36 - // controller: MobileScannerController(  
37 - // torchEnabled: true,  
38 - // facing: CameraFacing.front,  
39 - // ),  
40 - onDetect: (barcode, args) {  
41 - if (this.barcode != barcode.rawValue) {  
42 - setState(() {  
43 - this.barcode = barcode.rawValue;  
44 - });  
45 - }  
46 - }),  
47 - Align(  
48 - alignment: Alignment.bottomCenter,  
49 - child: Container(  
50 - alignment: Alignment.bottomCenter,  
51 - height: 100,  
52 - color: Colors.black.withOpacity(0.4),  
53 - child: Row(  
54 - crossAxisAlignment: CrossAxisAlignment.center,  
55 - mainAxisAlignment: MainAxisAlignment.spaceEvenly,  
56 - children: [  
57 - IconButton(  
58 - color: Colors.white,  
59 - icon: ValueListenableBuilder(  
60 - valueListenable: controller.torchState,  
61 - builder: (context, state, child) {  
62 - switch (state as TorchState) {  
63 - case TorchState.off:  
64 - return const Icon(Icons.flash_off,  
65 - color: Colors.grey);  
66 - case TorchState.on:  
67 - return const Icon(Icons.flash_on,  
68 - color: Colors.yellow);  
69 - }  
70 - },  
71 - ),  
72 - iconSize: 32.0,  
73 - onPressed: () => controller.toggleTorch(),  
74 - ),  
75 - Center(  
76 - child: SizedBox(  
77 - width: MediaQuery.of(context).size.width - 180,  
78 - height: 50,  
79 - child: FittedBox(  
80 - child: Text(  
81 - barcode ?? 'Scan something!',  
82 - overflow: TextOverflow.fade,  
83 - style: Theme.of(context)  
84 - .textTheme  
85 - .headline4!  
86 - .copyWith(color: Colors.white),  
87 - ),  
88 - ),  
89 - ),  
90 - ),  
91 - IconButton(  
92 - color: Colors.white,  
93 - icon: ValueListenableBuilder(  
94 - valueListenable: controller.cameraFacingState,  
95 - builder: (context, state, child) {  
96 - switch (state as CameraFacing) {  
97 - case CameraFacing.front:  
98 - return const Icon(Icons.camera_front);  
99 - case CameraFacing.back:  
100 - return const Icon(Icons.camera_rear);  
101 - }  
102 - },  
103 - ),  
104 - iconSize: 32.0,  
105 - onPressed: () => controller.switchCamera(),  
106 - ),  
107 - IconButton(  
108 - color: Colors.white,  
109 - icon: Icon(Icons.browse_gallery),  
110 - iconSize: 32.0,  
111 - onPressed: () async {  
112 - final ImagePicker _picker = ImagePicker();  
113 - // Pick an image  
114 - final XFile? image = await _picker.pickImage(source: ImageSource.gallery);  
115 - if (image != null) {  
116 - controller.analyzeImage(image.path);  
117 - }  
118 - },  
119 - ),  
120 - ],  
121 - ),  
122 - ),  
123 - ),  
124 -  
125 - // Container(  
126 - // alignment: Alignment.bottomCenter,  
127 - // margin: EdgeInsets.only(bottom: 80.0),  
128 - // child: IconButton(  
129 - // icon: ValueListenableBuilder(  
130 - // valueListenable: cameraController.torchState,  
131 - // builder: (context, state, child) {  
132 - // final color =  
133 - // state == TorchState.off ? Colors.grey : Colors.white;  
134 - // return Icon(Icons.bolt, color: color);  
135 - // },  
136 - // ),  
137 - // iconSize: 32.0,  
138 - // onPressed: () => cameraController.torch(),  
139 - // ),  
140 - // ),  
141 - ],  
142 - );  
143 - }), 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,
  19 + crossAxisAlignment: CrossAxisAlignment.center,
  20 + children: [
  21 + ElevatedButton(
  22 + onPressed: () {
  23 + Navigator.of(context).push(MaterialPageRoute(
  24 + builder: (context) => const BarcodeScannerWithController(),
  25 + ));
  26 + },
  27 + child: const Text('MobileScanner with Controller'),
  28 + ),
  29 + ElevatedButton(
  30 + onPressed: () {
  31 + Navigator.of(context).push(MaterialPageRoute(
  32 + builder: (context) => const BarcodeScannerWithoutController(),
  33 + ));
  34 + },
  35 + child: const Text('MobileScanner without Controller'),
  36 + ),
  37 + ],
  38 + ),
144 ), 39 ),
145 ); 40 );
146 } 41 }
147 -  
148 - @override  
149 - void dispose() {  
150 - // cameraController.dispose();  
151 - super.dispose();  
152 - }  
153 -  
154 - void display(Barcode barcode) {  
155 - Navigator.of(context).popAndPushNamed('display', arguments: barcode);  
156 - }  
157 } 42 }
158 -  
159 -// import 'package:flutter/material.dart';  
160 -// import 'package:flutter/rendering.dart';  
161 -// import 'package:mobile_scanner/mobile_scanner.dart';  
162 -//  
163 -// void main() {  
164 -// debugPaintSizeEnabled = false;  
165 -// runApp(HomePage());  
166 -// }  
167 -//  
168 -// class HomePage extends StatefulWidget {  
169 -// @override  
170 -// HomeState createState() => HomeState();  
171 -// }  
172 -//  
173 -// class HomeState extends State<HomePage> {  
174 -// @override  
175 -// Widget build(BuildContext context) {  
176 -// return MaterialApp(home: MyApp());  
177 -// }  
178 -// }  
179 -//  
180 -// class MyApp extends StatefulWidget {  
181 -// @override  
182 -// _MyAppState createState() => _MyAppState();  
183 -// }  
184 -//  
185 -// class _MyAppState extends State<MyApp> {  
186 -// String? qr;  
187 -// bool camState = false;  
188 -//  
189 -// @override  
190 -// initState() {  
191 -// super.initState();  
192 -// }  
193 -//  
194 -// @override  
195 -// Widget build(BuildContext context) {  
196 -// return Scaffold(  
197 -// appBar: AppBar(  
198 -// title: Text('Plugin example app'),  
199 -// ),  
200 -// body: Center(  
201 -// child: Column(  
202 -// crossAxisAlignment: CrossAxisAlignment.center,  
203 -// mainAxisAlignment: MainAxisAlignment.center,  
204 -// children: <Widget>[  
205 -// Expanded(  
206 -// child: camState  
207 -// ? Center(  
208 -// child: SizedBox(  
209 -// width: 300.0,  
210 -// height: 600.0,  
211 -// child: MobileScanner(  
212 -// onError: (context, error) => Text(  
213 -// error.toString(),  
214 -// style: TextStyle(color: Colors.red),  
215 -// ),  
216 -// qrCodeCallback: (code) {  
217 -// setState(() {  
218 -// qr = code;  
219 -// });  
220 -// },  
221 -// child: Container(  
222 -// decoration: BoxDecoration(  
223 -// color: Colors.transparent,  
224 -// border: Border.all(  
225 -// color: Colors.orange,  
226 -// width: 10.0,  
227 -// style: BorderStyle.solid),  
228 -// ),  
229 -// ),  
230 -// ),  
231 -// ),  
232 -// )  
233 -// : Center(child: Text("Camera inactive"))),  
234 -// Text("QRCODE: $qr"),  
235 -// ],  
236 -// ),  
237 -// ),  
238 -// floatingActionButton: FloatingActionButton(  
239 -// child: Text(  
240 -// "press me",  
241 -// textAlign: TextAlign.center,  
242 -// ),  
243 -// onPressed: () {  
244 -// setState(() {  
245 -// camState = !camState;  
246 -// });  
247 -// }),  
248 -// );  
249 -// }  
250 -// }  
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 = (
@@ -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/material.dart'; 1 import 'package:flutter/material.dart';
2 import 'package:mobile_scanner/mobile_scanner.dart'; 2 import 'package:mobile_scanner/mobile_scanner.dart';
3 3
4 -import 'mobile_scanner_arguments.dart';  
5 -  
6 enum Ratio { ratio_4_3, ratio_16_9 } 4 enum Ratio { ratio_4_3, ratio_16_9 }
7 5
8 /// A widget showing a live camera preview. 6 /// A widget showing a live camera preview.
@@ -31,8 +29,7 @@ class MobileScanner extends StatefulWidget { @@ -31,8 +29,7 @@ class MobileScanner extends StatefulWidget {
31 this.onDetect, 29 this.onDetect,
32 this.controller, 30 this.controller,
33 this.fit = BoxFit.cover, 31 this.fit = BoxFit.cover,
34 - }) : assert((controller != null)),  
35 - super(key: key); 32 + }) : super(key: key);
36 33
37 @override 34 @override
38 State<MobileScanner> createState() => _MobileScannerState(); 35 State<MobileScanner> createState() => _MobileScannerState();
@@ -40,33 +37,32 @@ class MobileScanner extends StatefulWidget { @@ -40,33 +37,32 @@ class MobileScanner extends StatefulWidget {
40 37
41 class _MobileScannerState extends State<MobileScanner> 38 class _MobileScannerState extends State<MobileScanner>
42 with WidgetsBindingObserver { 39 with WidgetsBindingObserver {
43 - bool onScreen = true;  
44 late MobileScannerController controller; 40 late MobileScannerController controller;
45 41
46 @override 42 @override
47 void initState() { 43 void initState() {
48 super.initState(); 44 super.initState();
  45 + WidgetsBinding.instance?.addObserver(this);
49 controller = widget.controller ?? MobileScannerController(); 46 controller = widget.controller ?? MobileScannerController();
50 } 47 }
51 48
52 @override 49 @override
53 void didChangeAppLifecycleState(AppLifecycleState state) { 50 void didChangeAppLifecycleState(AppLifecycleState state) {
54 - if (state == AppLifecycleState.resumed) {  
55 - setState(() => onScreen = true);  
56 - } else {  
57 - if (onScreen) { 51 + switch (state) {
  52 + case AppLifecycleState.resumed:
  53 + if (!controller.isStarting) controller.start();
  54 + break;
  55 + case AppLifecycleState.inactive:
  56 + case AppLifecycleState.paused:
  57 + case AppLifecycleState.detached:
58 controller.stop(); 58 controller.stop();
59 - }  
60 - setState(() {  
61 - onScreen = false;  
62 - }); 59 + break;
63 } 60 }
64 } 61 }
65 62
66 @override 63 @override
67 Widget build(BuildContext context) { 64 Widget build(BuildContext context) {
68 return LayoutBuilder(builder: (context, BoxConstraints constraints) { 65 return LayoutBuilder(builder: (context, BoxConstraints constraints) {
69 - if (!onScreen) return const Text("Camera Paused.");  
70 return ValueListenableBuilder( 66 return ValueListenableBuilder(
71 valueListenable: controller.args, 67 valueListenable: controller.args,
72 builder: (context, value, child) { 68 builder: (context, value, child) {
@@ -114,7 +110,8 @@ class _MobileScannerState extends State<MobileScanner> @@ -114,7 +110,8 @@ class _MobileScannerState extends State<MobileScanner>
114 110
115 @override 111 @override
116 void dispose() { 112 void dispose() {
117 - if (widget.controller == null) controller.dispose(); 113 + controller.dispose();
  114 + WidgetsBinding.instance?.removeObserver(this);
118 super.dispose(); 115 super.dispose();
119 } 116 }
120 } 117 }
@@ -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,9 +61,9 @@ class MobileScannerController { @@ -62,9 +61,9 @@ 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),  
67 - ); 64 + // onListen: () => setAnalyzeMode(AnalyzeMode.barcode.index),
  65 + // onCancel: () => setAnalyzeMode(AnalyzeMode.none.index),
  66 + );
68 67
69 start(); 68 start();
70 69
@@ -94,20 +93,27 @@ class MobileScannerController { @@ -94,20 +93,27 @@ 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 + bool isStarting = false;
  106 +
105 /// Start barcode scanning. This will first check if the required permissions 107 /// Start barcode scanning. This will first check if the required permissions
106 /// are set. 108 /// are set.
107 Future<void> start() async { 109 Future<void> start() async {
108 ensure('startAsync'); 110 ensure('startAsync');
  111 + if (isStarting) {
  112 + throw Exception('mobile_scanner: Called start() while already starting.');
  113 + }
  114 + isStarting = true;
  115 + // setAnalyzeMode(AnalyzeMode.barcode.index);
109 116
110 - setAnalyzeMode(AnalyzeMode.barcode.index);  
111 // Check authorization status 117 // Check authorization status
112 MobileScannerState state = 118 MobileScannerState state =
113 MobileScannerState.values[await methodChannel.invokeMethod('state')]; 119 MobileScannerState.values[await methodChannel.invokeMethod('state')];
@@ -118,6 +124,7 @@ class MobileScannerController { @@ -118,6 +124,7 @@ class MobileScannerController {
118 result ? MobileScannerState.authorized : MobileScannerState.denied; 124 result ? MobileScannerState.authorized : MobileScannerState.denied;
119 break; 125 break;
120 case MobileScannerState.denied: 126 case MobileScannerState.denied:
  127 + isStarting = false;
121 throw PlatformException(code: 'NO ACCESS'); 128 throw PlatformException(code: 'NO ACCESS');
122 case MobileScannerState.authorized: 129 case MobileScannerState.authorized:
123 break; 130 break;
@@ -132,10 +139,19 @@ class MobileScannerController { @@ -132,10 +139,19 @@ class MobileScannerController {
132 if (torchEnabled != null) arguments['torch'] = torchEnabled; 139 if (torchEnabled != null) arguments['torch'] = torchEnabled;
133 140
134 // Start the camera with arguments 141 // Start the camera with arguments
135 - final Map<String, dynamic>? startResult = await methodChannel  
136 - .invokeMapMethod<String, dynamic>('start', arguments); 142 + Map<String, dynamic>? startResult = {};
  143 + try {
  144 + startResult = await methodChannel.invokeMapMethod<String, dynamic>(
  145 + 'start', arguments);
  146 + } on PlatformException catch (error) {
  147 + debugPrint('${error.code}: ${error.message}');
  148 + isStarting = false;
  149 + // setAnalyzeMode(AnalyzeMode.none.index);
  150 + return;
  151 + }
137 152
138 if (startResult == null) { 153 if (startResult == null) {
  154 + isStarting = false;
139 throw PlatformException(code: 'INITIALIZATION ERROR'); 155 throw PlatformException(code: 'INITIALIZATION ERROR');
140 } 156 }
141 157
@@ -144,19 +160,35 @@ class MobileScannerController { @@ -144,19 +160,35 @@ class MobileScannerController {
144 textureId: startResult['textureId'], 160 textureId: startResult['textureId'],
145 size: toSize(startResult['size']), 161 size: toSize(startResult['size']),
146 hasTorch: hasTorch); 162 hasTorch: hasTorch);
  163 + isStarting = false;
147 } 164 }
148 165
149 - Future<void> stop() async => await methodChannel.invokeMethod('stop'); 166 + Future<void> stop() async {
  167 + try {
  168 + await methodChannel.invokeMethod('stop');
  169 + } on PlatformException catch (error) {
  170 + debugPrint('${error.code}: ${error.message}');
  171 + }
  172 + }
150 173
151 /// Switches the torch on or off. 174 /// Switches the torch on or off.
152 /// 175 ///
153 /// Only works if torch is available. 176 /// Only works if torch is available.
154 - void toggleTorch() { 177 + Future<void> toggleTorch() async {
155 ensure('toggleTorch'); 178 ensure('toggleTorch');
156 - if (!hasTorch) return; 179 + if (!hasTorch) {
  180 + debugPrint('Device has no torch/flash.');
  181 + return;
  182 + }
  183 +
157 TorchState state = 184 TorchState state =
158 torchState.value == TorchState.off ? TorchState.on : TorchState.off; 185 torchState.value == TorchState.off ? TorchState.on : TorchState.off;
159 - methodChannel.invokeMethod('torch', state.index); 186 +
  187 + try {
  188 + await methodChannel.invokeMethod('torch', state.index);
  189 + } on PlatformException catch (error) {
  190 + debugPrint('${error.code}: ${error.message}');
  191 + }
160 } 192 }
161 193
162 /// Switches the torch on or off. 194 /// Switches the torch on or off.
@@ -164,10 +196,16 @@ class MobileScannerController { @@ -164,10 +196,16 @@ class MobileScannerController {
164 /// Only works if torch is available. 196 /// Only works if torch is available.
165 Future<void> switchCamera() async { 197 Future<void> switchCamera() async {
166 ensure('switchCamera'); 198 ensure('switchCamera');
167 - await stop(); 199 + try {
  200 + await methodChannel.invokeMethod('stop');
  201 + } on PlatformException catch (error) {
  202 + debugPrint(
  203 + '${error.code}: camera is stopped! Please start before switching camera.');
  204 + return;
  205 + }
168 facing = 206 facing =
169 facing == CameraFacing.back ? CameraFacing.front : CameraFacing.back; 207 facing == CameraFacing.back ? CameraFacing.front : CameraFacing.back;
170 - start(); 208 + await start();
171 } 209 }
172 210
173 Future<void> analyzeImage(dynamic path) async { 211 Future<void> analyzeImage(dynamic path) async {
@@ -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 ///
@@ -165,22 +165,22 @@ class ContactInfo { @@ -165,22 +165,22 @@ class ContactInfo {
165 /// Gets contact person's organization. 165 /// Gets contact person's organization.
166 /// 166 ///
167 /// Returns null if not available. 167 /// Returns null if not available.
168 - final String organization; 168 + final String? organization;
169 169
170 /// Gets contact person's phones. 170 /// Gets contact person's phones.
171 /// 171 ///
172 /// Returns an empty list if nothing found. 172 /// Returns an empty list if nothing found.
173 - final List<Phone> phones; 173 + final List<Phone>? phones;
174 174
175 /// Gets contact person's title. 175 /// Gets contact person's title.
176 /// 176 ///
177 /// Returns null if not available. 177 /// Returns null if not available.
178 - final String title; 178 + final String? title;
179 179
180 /// Gets contact person's urls. 180 /// Gets contact person's urls.
181 /// 181 ///
182 /// Returns an empty list if nothing found. 182 /// Returns an empty list if nothing found.
183 - final List<String> urls; 183 + final List<String>? urls;
184 184
185 /// Create a [ContactInfo] from native data. 185 /// Create a [ContactInfo] from native data.
186 ContactInfo.fromNative(Map<dynamic, dynamic> data) 186 ContactInfo.fromNative(Map<dynamic, dynamic> data)
@@ -202,7 +202,9 @@ class Address { @@ -202,7 +202,9 @@ class Address {
202 final List<String> addressLines; 202 final List<String> addressLines;
203 203
204 /// Gets type of the address. 204 /// Gets type of the address.
205 - final AddressType type; 205 + ///
  206 + /// Returns null if not available.
  207 + final AddressType? type;
206 208
207 /// Create a [Address] from native data. 209 /// Create a [Address] from native data.
208 Address.fromNative(Map<dynamic, dynamic> data) 210 Address.fromNative(Map<dynamic, dynamic> data)
@@ -215,37 +217,37 @@ class PersonName { @@ -215,37 +217,37 @@ class PersonName {
215 /// Gets first name. 217 /// Gets first name.
216 /// 218 ///
217 /// Returns null if not available. 219 /// Returns null if not available.
218 - final String first; 220 + final String? first;
219 221
220 /// Gets middle name. 222 /// Gets middle name.
221 /// 223 ///
222 /// Returns null if not available. 224 /// Returns null if not available.
223 - final String middle; 225 + final String? middle;
224 226
225 /// Gets last name. 227 /// Gets last name.
226 /// 228 ///
227 /// Returns null if not available. 229 /// Returns null if not available.
228 - final String last; 230 + final String? last;
229 231
230 /// Gets prefix of the name. 232 /// Gets prefix of the name.
231 /// 233 ///
232 /// Returns null if not available. 234 /// Returns null if not available.
233 - final String prefix; 235 + final String? prefix;
234 236
235 /// Gets suffix of the person's name. 237 /// Gets suffix of the person's name.
236 /// 238 ///
237 /// Returns null if not available. 239 /// Returns null if not available.
238 - final String suffix; 240 + final String? suffix;
239 241
240 /// Gets the properly formatted name. 242 /// Gets the properly formatted name.
241 /// 243 ///
242 /// Returns null if not available. 244 /// Returns null if not available.
243 - final String formattedName; 245 + final String? formattedName;
244 246
245 /// 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.
246 /// 248 ///
247 /// Returns null if not available. 249 /// Returns null if not available.
248 - final String pronunciation; 250 + final String? pronunciation;
249 251
250 /// Create a [PersonName] from native data. 252 /// Create a [PersonName] from native data.
251 PersonName.fromNative(Map<dynamic, dynamic> data) 253 PersonName.fromNative(Map<dynamic, dynamic> data)
@@ -263,74 +265,74 @@ class DriverLicense { @@ -263,74 +265,74 @@ class DriverLicense {
263 /// Gets city of holder's address. 265 /// Gets city of holder's address.
264 /// 266 ///
265 /// Returns null if not available. 267 /// Returns null if not available.
266 - final String addressCity; 268 + final String? addressCity;
267 269
268 /// Gets state of holder's address. 270 /// Gets state of holder's address.
269 /// 271 ///
270 /// Returns null if not available. 272 /// Returns null if not available.
271 - final String addressState; 273 + final String? addressState;
272 274
273 /// Gets holder's street address. 275 /// Gets holder's street address.
274 /// 276 ///
275 /// Returns null if not available. 277 /// Returns null if not available.
276 - final String addressStreet; 278 + final String? addressStreet;
277 279
278 /// Gets postal code of holder's address. 280 /// Gets postal code of holder's address.
279 /// 281 ///
280 /// Returns null if not available. 282 /// Returns null if not available.
281 - final String addressZip; 283 + final String? addressZip;
282 284
283 /// Gets birth date of the holder. 285 /// Gets birth date of the holder.
284 /// 286 ///
285 /// Returns null if not available. 287 /// Returns null if not available.
286 - final String birthDate; 288 + final String? birthDate;
287 289
288 /// Gets "DL" for driver licenses, "ID" for ID cards. 290 /// Gets "DL" for driver licenses, "ID" for ID cards.
289 /// 291 ///
290 /// Returns null if not available. 292 /// Returns null if not available.
291 - final String documentType; 293 + final String? documentType;
292 294
293 /// Gets expiry date of the license. 295 /// Gets expiry date of the license.
294 /// 296 ///
295 /// Returns null if not available. 297 /// Returns null if not available.
296 - final String expiryDate; 298 + final String? expiryDate;
297 299
298 /// Gets holder's first name. 300 /// Gets holder's first name.
299 /// 301 ///
300 /// Returns null if not available. 302 /// Returns null if not available.
301 - final String firstName; 303 + final String? firstName;
302 304
303 /// Gets holder's gender. 1 - male, 2 - female. 305 /// Gets holder's gender. 1 - male, 2 - female.
304 /// 306 ///
305 /// Returns null if not available. 307 /// Returns null if not available.
306 - final String gender; 308 + final String? gender;
307 309
308 /// Gets issue date of the license. 310 /// Gets issue date of the license.
309 /// 311 ///
310 /// 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.
311 /// 313 ///
312 /// Returns null if not available. 314 /// Returns null if not available.
313 - final String issueDate; 315 + final String? issueDate;
314 316
315 /// 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.
316 /// 318 ///
317 /// Returns null if not available. 319 /// Returns null if not available.
318 - final String issuingCountry; 320 + final String? issuingCountry;
319 321
320 /// Gets holder's last name. 322 /// Gets holder's last name.
321 /// 323 ///
322 /// Returns null if not available. 324 /// Returns null if not available.
323 - final String lastName; 325 + final String? lastName;
324 326
325 /// Gets driver license ID number. 327 /// Gets driver license ID number.
326 /// 328 ///
327 /// Returns null if not available. 329 /// Returns null if not available.
328 - final String licenseNumber; 330 + final String? licenseNumber;
329 331
330 /// Gets holder's middle name. 332 /// Gets holder's middle name.
331 /// 333 ///
332 /// Returns null if not available. 334 /// Returns null if not available.
333 - final String middleName; 335 + final String? middleName;
334 336
335 /// Create a [DriverLicense] from native data. 337 /// Create a [DriverLicense] from native data.
336 DriverLicense.fromNative(Map<dynamic, dynamic> data) 338 DriverLicense.fromNative(Map<dynamic, dynamic> data)
@@ -355,22 +357,23 @@ class Email { @@ -355,22 +357,23 @@ class Email {
355 /// Gets email's address. 357 /// Gets email's address.
356 /// 358 ///
357 /// Returns null if not available. 359 /// Returns null if not available.
358 - final String address; 360 + final String? address;
359 361
360 /// Gets email's body. 362 /// Gets email's body.
361 /// 363 ///
362 /// Returns null if not available. 364 /// Returns null if not available.
363 - final String body; 365 + final String? body;
364 366
365 /// Gets email's subject. 367 /// Gets email's subject.
366 /// 368 ///
367 /// Returns null if not available. 369 /// Returns null if not available.
368 - final String subject; 370 + final String? subject;
369 371
370 /// Gets type of the email. 372 /// Gets type of the email.
371 /// 373 ///
372 /// See also [EmailType]. 374 /// See also [EmailType].
373 - final EmailType type; 375 + /// Returns null if not available.
  376 + final EmailType? type;
374 377
375 /// Create a [Email] from native data. 378 /// Create a [Email] from native data.
376 Email.fromNative(Map<dynamic, dynamic> data) 379 Email.fromNative(Map<dynamic, dynamic> data)
@@ -383,10 +386,10 @@ class Email { @@ -383,10 +386,10 @@ class Email {
383 /// GPS coordinates from a 'GEO:' or similar QRCode type. 386 /// GPS coordinates from a 'GEO:' or similar QRCode type.
384 class GeoPoint { 387 class GeoPoint {
385 /// Gets the latitude. 388 /// Gets the latitude.
386 - final double latitude; 389 + final double? latitude;
387 390
388 /// Gets the longitude. 391 /// Gets the longitude.
389 - final double longitude; 392 + final double? longitude;
390 393
391 /// Create a [GeoPoint] from native data. 394 /// Create a [GeoPoint] from native data.
392 GeoPoint.fromNative(Map<dynamic, dynamic> data) 395 GeoPoint.fromNative(Map<dynamic, dynamic> data)
@@ -399,12 +402,13 @@ class Phone { @@ -399,12 +402,13 @@ class Phone {
399 /// Gets phone number. 402 /// Gets phone number.
400 /// 403 ///
401 /// Returns null if not available. 404 /// Returns null if not available.
402 - final String number; 405 + final String? number;
403 406
404 /// Gets type of the phone number. 407 /// Gets type of the phone number.
405 /// 408 ///
406 /// See also [PhoneType]. 409 /// See also [PhoneType].
407 - final PhoneType type; 410 + /// Returns null if not available.
  411 + final PhoneType? type;
408 412
409 /// Create a [Phone] from native data. 413 /// Create a [Phone] from native data.
410 Phone.fromNative(Map<dynamic, dynamic> data) 414 Phone.fromNative(Map<dynamic, dynamic> data)
@@ -417,12 +421,12 @@ class SMS { @@ -417,12 +421,12 @@ class SMS {
417 /// Gets the message content of the sms. 421 /// Gets the message content of the sms.
418 /// 422 ///
419 /// Returns null if not available. 423 /// Returns null if not available.
420 - final String message; 424 + final String? message;
421 425
422 /// Gets the phone number of the sms. 426 /// Gets the phone number of the sms.
423 /// 427 ///
424 /// Returns null if not available. 428 /// Returns null if not available.
425 - final String phoneNumber; 429 + final String? phoneNumber;
426 430
427 /// Create a [SMS] from native data. 431 /// Create a [SMS] from native data.
428 SMS.fromNative(Map<dynamic, dynamic> data) 432 SMS.fromNative(Map<dynamic, dynamic> data)
@@ -435,12 +439,12 @@ class UrlBookmark { @@ -435,12 +439,12 @@ class UrlBookmark {
435 /// Gets the title of the bookmark. 439 /// Gets the title of the bookmark.
436 /// 440 ///
437 /// Returns null if not available. 441 /// Returns null if not available.
438 - final String title; 442 + final String? title;
439 443
440 /// Gets the url of the bookmark. 444 /// Gets the url of the bookmark.
441 /// 445 ///
442 /// Returns null if not available. 446 /// Returns null if not available.
443 - final String url; 447 + final String? url;
444 448
445 /// Create a [UrlBookmark] from native data. 449 /// Create a [UrlBookmark] from native data.
446 UrlBookmark.fromNative(Map<dynamic, dynamic> data) 450 UrlBookmark.fromNative(Map<dynamic, dynamic> data)
@@ -458,12 +462,12 @@ class WiFi { @@ -458,12 +462,12 @@ class WiFi {
458 /// Gets the ssid of the WIFI. 462 /// Gets the ssid of the WIFI.
459 /// 463 ///
460 /// Returns null if not available. 464 /// Returns null if not available.
461 - final String ssid; 465 + final String? ssid;
462 466
463 /// Gets the password of the WIFI. 467 /// Gets the password of the WIFI.
464 /// 468 ///
465 /// Returns null if not available. 469 /// Returns null if not available.
466 - final String password; 470 + final String? password;
467 471
468 /// Create a [WiFi] from native data. 472 /// Create a [WiFi] from native data.
469 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
@@ -232,13 +252,19 @@ public class MobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHandler, @@ -232,13 +252,19 @@ public class MobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHandler,
232 result(FlutterError(code: error.localizedDescription, message: nil, details: nil)) 252 result(FlutterError(code: error.localizedDescription, message: nil, details: nil))
233 } 253 }
234 } 254 }
235 -  
236 - func switchAnalyzeMode(_ call: FlutterMethodCall, _ result: @escaping FlutterResult) {  
237 - analyzeMode = call.arguments as! Int  
238 - result(nil)  
239 - }  
240 - 255 +
  256 +// func switchAnalyzeMode(_ call: FlutterMethodCall, _ result: @escaping FlutterResult) {
  257 +// analyzeMode = call.arguments as! Int
  258 +// result(nil)
  259 +// }
  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 barcode and QR code scanner for Flutter based on MLKit. Uses CameraX on Android, AVFoundation on iOS and Apple Vision & AVFoundation on macOS. 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.1 3 +version: 0.1.3
4 repository: https://github.com/juliansteenbakker/mobile_scanner 4 repository: https://github.com/juliansteenbakker/mobile_scanner
5 5
6 environment: 6 environment: