Navaron Bracke
Committed by GitHub

Merge pull request #798 from Spyy004/master

feat: Scanner with overlay screen added
@@ -155,7 +155,7 @@ @@ -155,7 +155,7 @@
155 97C146E61CF9000F007C117D /* Project object */ = { 155 97C146E61CF9000F007C117D /* Project object */ = {
156 isa = PBXProject; 156 isa = PBXProject;
157 attributes = { 157 attributes = {
158 - LastUpgradeCheck = 1300; 158 + LastUpgradeCheck = 1430;
159 ORGANIZATIONNAME = ""; 159 ORGANIZATIONNAME = "";
160 TargetAttributes = { 160 TargetAttributes = {
161 97C146ED1CF9000F007C117D = { 161 97C146ED1CF9000F007C117D = {
1 <?xml version="1.0" encoding="UTF-8"?> 1 <?xml version="1.0" encoding="UTF-8"?>
2 <Scheme 2 <Scheme
3 - LastUpgradeVersion = "1300" 3 + LastUpgradeVersion = "1430"
4 version = "1.3"> 4 version = "1.3">
5 <BuildAction 5 <BuildAction
6 parallelizeBuildables = "YES" 6 parallelizeBuildables = "YES"
@@ -51,5 +51,10 @@ @@ -51,5 +51,10 @@
51 </array> 51 </array>
52 <key>UIViewControllerBasedStatusBarAppearance</key> 52 <key>UIViewControllerBasedStatusBarAppearance</key>
53 <false/> 53 <false/>
  54 + <key>NSCameraUsageDescription</key>
  55 + <string>This app needs camera access to scan QR codes</string>
  56 +
  57 + <key>NSPhotoLibraryUsageDescription</key>
  58 + <string>This app needs photos access to get QR code from photo library</string>
54 </dict> 59 </dict>
55 </plist> 60 </plist>
@@ -6,6 +6,7 @@ import 'package:mobile_scanner_example/barcode_scanner_returning_image.dart'; @@ -6,6 +6,7 @@ import 'package:mobile_scanner_example/barcode_scanner_returning_image.dart';
6 import 'package:mobile_scanner_example/barcode_scanner_window.dart'; 6 import 'package:mobile_scanner_example/barcode_scanner_window.dart';
7 import 'package:mobile_scanner_example/barcode_scanner_without_controller.dart'; 7 import 'package:mobile_scanner_example/barcode_scanner_without_controller.dart';
8 import 'package:mobile_scanner_example/barcode_scanner_zoom.dart'; 8 import 'package:mobile_scanner_example/barcode_scanner_zoom.dart';
  9 +import 'package:mobile_scanner_example/mobile_scanner_overlay.dart';
9 10
10 void main() => runApp(const MaterialApp(home: MyHome())); 11 void main() => runApp(const MaterialApp(home: MyHome()));
11 12
@@ -95,6 +96,16 @@ class MyHome extends StatelessWidget { @@ -95,6 +96,16 @@ class MyHome extends StatelessWidget {
95 }, 96 },
96 child: const Text('MobileScanner pageView'), 97 child: const Text('MobileScanner pageView'),
97 ), 98 ),
  99 + ElevatedButton(
  100 + onPressed: () {
  101 + Navigator.of(context).push(
  102 + MaterialPageRoute(
  103 + builder: (context) => BarcodeScannerWithOverlay(),
  104 + ),
  105 + );
  106 + },
  107 + child: const Text('MobileScanner with Overlay'),
  108 + ),
98 ], 109 ],
99 ), 110 ),
100 ), 111 ),
1 -//TODO: Create example with scanner overlay  
2 -  
3 -// import 'dart:ui';  
4 -//  
5 -// import 'package:flutter/material.dart';  
6 -// import 'package:mobile_scanner/mobile_scanner.dart';  
7 -//  
8 -// void main() {  
9 -// runApp(const AnalyzeView());  
10 -// }  
11 -//  
12 -// class AnalyzeView extends StatefulWidget {  
13 -// const AnalyzeView({Key? key}) : super(key: key);  
14 -//  
15 -// @override  
16 -// _AnalyzeViewState createState() => _AnalyzeViewState();  
17 -// }  
18 -//  
19 -// class _AnalyzeViewState extends State<AnalyzeView>  
20 -// with SingleTickerProviderStateMixin {  
21 -// List<Offset> points = [];  
22 -//  
23 -// // CameraController cameraController = CameraController(context, width: 320, height: 150);  
24 -//  
25 -// String? barcode;  
26 -//  
27 -// @override  
28 -// Widget build(BuildContext context) {  
29 -// return MaterialApp(  
30 -// home: Scaffold(  
31 -// body: Builder(builder: (context) {  
32 -// return Stack(  
33 -// children: [  
34 -// MobileScanner(  
35 -// // fitScreen: false,  
36 -// // controller: cameraController,  
37 -// onDetect: (barcode, args) {  
38 -// if (this.barcode != barcode.rawValue) {  
39 -// this.barcode = barcode.rawValue;  
40 -// if (barcode.corners != null) {  
41 -// ScaffoldMessenger.of(context).showSnackBar(SnackBar(  
42 -// content: Text(barcode.rawValue),  
43 -// duration: const Duration(milliseconds: 200),  
44 -// animation: null,  
45 -// ));  
46 -// setState(() {  
47 -// final List<Offset> points = [];  
48 -// // double factorWidth = args.size.width / 520;  
49 -// // double factorHeight = wanted / args.size.height;  
50 -// final size = MediaQuery.of(context).devicePixelRatio;  
51 -// debugPrint('Size: ${barcode.corners}');  
52 -// for (var point in barcode.corners!) {  
53 -// final adjustedWith = point.dx;  
54 -// final adjustedHeight = point.dy;  
55 -// points.add(  
56 -// Offset(adjustedWith / size, adjustedHeight / size));  
57 -// // points.add(Offset((point.dx ) / size,  
58 -// // (point.dy) / size));  
59 -// // final differenceWidth = (args.wantedSize!.width - args.size.width) / 2;  
60 -// // final differenceHeight = (args.wantedSize!.height - args.size.height) / 2;  
61 -// // points.add(Offset((point.dx + differenceWidth) / size,  
62 -// // (point.dy + differenceHeight) / size));  
63 -// }  
64 -// this.points = points;  
65 -// });  
66 -// }  
67 -// }  
68 -// // Default 640 x480  
69 -// }),  
70 -// CustomPaint(  
71 -// painter: OpenPainter(points),  
72 -// ),  
73 -// // Container(  
74 -// // alignment: Alignment.bottomCenter,  
75 -// // margin: EdgeInsets.only(bottom: 80.0),  
76 -// // child: IconButton(  
77 -// // icon: ValueListenableBuilder(  
78 -// // valueListenable: cameraController.torchState,  
79 -// // builder: (context, state, child) {  
80 -// // final color =  
81 -// // state == TorchState.off ? Colors.grey : Colors.white;  
82 -// // return Icon(Icons.bolt, color: color);  
83 -// // },  
84 -// // ),  
85 -// // iconSize: 32.0,  
86 -// // onPressed: () => cameraController.torch(),  
87 -// // ),  
88 -// // ),  
89 -// ],  
90 -// );  
91 -// }),  
92 -// ),  
93 -// );  
94 -// }  
95 -//  
96 -// @override  
97 -// void dispose() {  
98 -// // cameraController.dispose();  
99 -// super.dispose();  
100 -// }  
101 -//  
102 -// void display(Barcode barcode) {  
103 -// Navigator.of(context).popAndPushNamed('display', arguments: barcode);  
104 -// }  
105 -// }  
106 -//  
107 -// class OpenPainter extends CustomPainter {  
108 -// final List<Offset> points;  
109 -//  
110 -// OpenPainter(this.points);  
111 -// @override  
112 -// void paint(Canvas canvas, Size size) {  
113 -// var paint1 = Paint()  
114 -// ..color = const Color(0xff63aa65)  
115 -// ..strokeWidth = 10;  
116 -// //draw points on canvas  
117 -// canvas.drawPoints(PointMode.points, points, paint1);  
118 -// }  
119 -//  
120 -// @override  
121 -// bool shouldRepaint(CustomPainter oldDelegate) => true;  
122 -// }  
123 -//  
124 -// class OpacityCurve extends Curve {  
125 -// @override  
126 -// double transform(double t) {  
127 -// if (t < 0.1) {  
128 -// return t * 10;  
129 -// } else if (t <= 0.9) {  
130 -// return 1.0;  
131 -// } else {  
132 -// return (1.0 - t) * 10;  
133 -// }  
134 -// }  
135 -// }  
136 -//  
137 -// // import 'package:flutter/material.dart';  
138 -// // import 'package:flutter/rendering.dart';  
139 -// // import 'package:mobile_scanner/mobile_scanner.dart';  
140 -// //  
141 -// // void main() {  
142 -// // debugPaintSizeEnabled = false;  
143 -// // runApp(HomePage());  
144 -// // }  
145 -// //  
146 -// // class HomePage extends StatefulWidget {  
147 -// // @override  
148 -// // HomeState createState() => HomeState();  
149 -// // }  
150 -// //  
151 -// // class HomeState extends State<HomePage> {  
152 -// // @override  
153 -// // Widget build(BuildContext context) {  
154 -// // return MaterialApp(home: MyApp());  
155 -// // }  
156 -// // }  
157 -// //  
158 -// // class MyApp extends StatefulWidget {  
159 -// // @override  
160 -// // _MyAppState createState() => _MyAppState();  
161 -// // }  
162 -// //  
163 -// // class _MyAppState extends State<MyApp> {  
164 -// // String? qr;  
165 -// // bool camState = false;  
166 -// //  
167 -// // @override  
168 -// // initState() {  
169 -// // super.initState();  
170 -// // }  
171 -// //  
172 -// // @override  
173 -// // Widget build(BuildContext context) {  
174 -// // return Scaffold(  
175 -// // appBar: AppBar(  
176 -// // title: Text('Plugin example app'),  
177 -// // ),  
178 -// // body: Center(  
179 -// // child: Column(  
180 -// // crossAxisAlignment: CrossAxisAlignment.center,  
181 -// // mainAxisAlignment: MainAxisAlignment.center,  
182 -// // children: <Widget>[  
183 -// // Expanded(  
184 -// // child: camState  
185 -// // ? Center(  
186 -// // child: SizedBox(  
187 -// // width: 300.0,  
188 -// // height: 600.0,  
189 -// // child: MobileScanner(  
190 -// // onError: (context, error) => Text(  
191 -// // error.toString(),  
192 -// // style: TextStyle(color: Colors.red),  
193 -// // ),  
194 -// // qrCodeCallback: (code) {  
195 -// // setState(() {  
196 -// // qr = code;  
197 -// // });  
198 -// // },  
199 -// // child: Container(  
200 -// // decoration: BoxDecoration(  
201 -// // color: Colors.transparent,  
202 -// // border: Border.all(  
203 -// // color: Colors.orange,  
204 -// // width: 10.0,  
205 -// // style: BorderStyle.solid),  
206 -// // ),  
207 -// // ),  
208 -// // ),  
209 -// // ),  
210 -// // )  
211 -// // : Center(child: Text("Camera inactive"))),  
212 -// // Text("QRCODE: $qr"),  
213 -// // ],  
214 -// // ),  
215 -// // ),  
216 -// // floatingActionButton: FloatingActionButton(  
217 -// // child: Text(  
218 -// // "press me",  
219 -// // textAlign: TextAlign.center,  
220 -// // ),  
221 -// // onPressed: () {  
222 -// // setState(() {  
223 -// // camState = !camState;  
224 -// // });  
225 -// // }),  
226 -// // );  
227 -// // }  
228 -// // } 1 +import 'package:flutter/material.dart';
  2 +import 'package:mobile_scanner/mobile_scanner.dart';
  3 +import 'package:mobile_scanner_example/scanner_error_widget.dart';
  4 +
  5 +class BarcodeScannerWithOverlay extends StatefulWidget {
  6 + @override
  7 + _BarcodeScannerWithOverlayState createState() =>
  8 + _BarcodeScannerWithOverlayState();
  9 +}
  10 +
  11 +class _BarcodeScannerWithOverlayState extends State<BarcodeScannerWithOverlay> {
  12 + String overlayText = "Please scan QR Code";
  13 + bool camStarted = false;
  14 +
  15 + final ValueNotifier<bool> torchStateNotifier = ValueNotifier<bool>(false);
  16 +
  17 + final MobileScannerController controller = MobileScannerController(
  18 + formats: const [BarcodeFormat.qrCode],
  19 + autoStart: false,
  20 + );
  21 +
  22 + @override
  23 + void dispose() {
  24 + controller.dispose();
  25 + super.dispose();
  26 + }
  27 +
  28 + void startCamera() {
  29 + try {
  30 + setState(() {
  31 + camStarted = !camStarted;
  32 + controller.start();
  33 + });
  34 + } on Exception catch (e) {
  35 + ScaffoldMessenger.of(context).showSnackBar(
  36 + SnackBar(
  37 + content: Text('Something went wrong! $e'),
  38 + backgroundColor: Colors.red,
  39 + ),
  40 + );
  41 + }
  42 + }
  43 +
  44 + void onBarcodeDetect(BarcodeCapture barcodeCapture) {
  45 + final barcode = barcodeCapture.barcodes.last;
  46 + setState(() {
  47 + overlayText = barcodeCapture.barcodes.last.displayValue ??
  48 + barcode.rawValue ??
  49 + 'Barcode has no displayable value';
  50 + });
  51 + }
  52 +
  53 + @override
  54 + Widget build(BuildContext context) {
  55 + final scanWindow = Rect.fromCenter(
  56 + center: MediaQuery.of(context).size.center(Offset.zero),
  57 + width: 200,
  58 + height: 200,
  59 + );
  60 +
  61 + return Scaffold(
  62 + appBar: AppBar(
  63 + title: const Text('Scanner with Overlay Example app'),
  64 + ),
  65 + body: Center(
  66 + child: Column(
  67 + mainAxisAlignment: MainAxisAlignment.center,
  68 + children: <Widget>[
  69 + Expanded(
  70 + child: camStarted
  71 + ? Stack(
  72 + fit: StackFit.expand,
  73 + children: [
  74 + Center(
  75 + child: MobileScanner(
  76 + fit: BoxFit.contain,
  77 + onDetect: onBarcodeDetect,
  78 + overlay: Padding(
  79 + padding: const EdgeInsets.all(16.0),
  80 + child: Align(
  81 + alignment: Alignment.bottomCenter,
  82 + child: Opacity(
  83 + opacity: 0.7,
  84 + child: Text(
  85 + overlayText,
  86 + style: const TextStyle(
  87 + backgroundColor: Colors.black26,
  88 + color: Colors.white,
  89 + fontWeight: FontWeight.bold,
  90 + fontSize: 24,
  91 + overflow: TextOverflow.ellipsis,
  92 + ),
  93 + maxLines: 1,
  94 + ),
  95 + ),
  96 + ),
  97 + ),
  98 + controller: controller,
  99 + scanWindow: scanWindow,
  100 + errorBuilder: (context, error, child) {
  101 + return ScannerErrorWidget(error: error);
  102 + },
  103 + ),
  104 + ),
  105 + CustomPaint(
  106 + painter: ScannerOverlay(scanWindow),
  107 + ),
  108 + Padding(
  109 + padding: const EdgeInsets.all(16.0),
  110 + child: Align(
  111 + alignment: Alignment.bottomCenter,
  112 + child: Row(
  113 + mainAxisAlignment: MainAxisAlignment.spaceEvenly,
  114 + children: [
  115 + ValueListenableBuilder<bool>(
  116 + valueListenable: torchStateNotifier,
  117 + builder: (context, isTorchOn, child) {
  118 + return IconButton(
  119 + onPressed: () {
  120 + controller.toggleTorch();
  121 + torchStateNotifier.value =
  122 + !torchStateNotifier.value;
  123 + },
  124 + icon: Icon(
  125 + Icons.flashlight_on,
  126 + color: isTorchOn
  127 + ? Colors.yellow
  128 + : Colors.black,
  129 + ),
  130 + );
  131 + },
  132 + ),
  133 + IconButton(
  134 + onPressed: () => controller.switchCamera(),
  135 + icon: const Icon(
  136 + Icons.cameraswitch_rounded,
  137 + color: Colors.white,
  138 + ),
  139 + ),
  140 + ],
  141 + ),
  142 + ),
  143 + ),
  144 + ],
  145 + )
  146 + : const Center(
  147 + child: Text("Tap on Camera to activate QR Scanner"),
  148 + ),
  149 + ),
  150 + ],
  151 + ),
  152 + ),
  153 + floatingActionButton: camStarted
  154 + ? null
  155 + : FloatingActionButton(
  156 + child: const Icon(
  157 + Icons.camera_alt,
  158 + ),
  159 + onPressed: () {
  160 + startCamera();
  161 + },
  162 + ),
  163 + );
  164 + }
  165 +}
  166 +
  167 +class ScannerOverlay extends CustomPainter {
  168 + ScannerOverlay(this.scanWindow);
  169 +
  170 + final Rect scanWindow;
  171 + final double borderRadius = 12.0;
  172 +
  173 + @override
  174 + void paint(Canvas canvas, Size size) {
  175 + final backgroundPath = Path()..addRect(Rect.largest);
  176 + final cutoutPath = Path()
  177 + ..addRRect(
  178 + RRect.fromRectAndCorners(
  179 + scanWindow,
  180 + topLeft: Radius.circular(borderRadius),
  181 + topRight: Radius.circular(borderRadius),
  182 + bottomLeft: Radius.circular(borderRadius),
  183 + bottomRight: Radius.circular(borderRadius),
  184 + ),
  185 + );
  186 +
  187 + final backgroundPaint = Paint()
  188 + ..color = Colors.black.withOpacity(0.5)
  189 + ..style = PaintingStyle.fill
  190 + ..blendMode = BlendMode.dstOut;
  191 +
  192 + final backgroundWithCutout = Path.combine(
  193 + PathOperation.difference,
  194 + backgroundPath,
  195 + cutoutPath,
  196 + );
  197 +
  198 + // Create a Paint object for the white border
  199 + final borderPaint = Paint()
  200 + ..color = Colors.white
  201 + ..style = PaintingStyle.stroke
  202 + ..strokeWidth = 4.0; // Adjust the border width as needed
  203 +
  204 + // Calculate the border rectangle with rounded corners
  205 +// Adjust the radius as needed
  206 + final borderRect = RRect.fromRectAndCorners(
  207 + scanWindow,
  208 + topLeft: Radius.circular(borderRadius),
  209 + topRight: Radius.circular(borderRadius),
  210 + bottomLeft: Radius.circular(borderRadius),
  211 + bottomRight: Radius.circular(borderRadius),
  212 + );
  213 +
  214 + // Draw the white border
  215 + canvas.drawPath(backgroundWithCutout, backgroundPaint);
  216 + canvas.drawRRect(borderRect, borderPaint);
  217 + }
  218 +
  219 + @override
  220 + bool shouldRepaint(covariant CustomPainter oldDelegate) {
  221 + return false;
  222 + }
  223 +}
@@ -202,7 +202,7 @@ @@ -202,7 +202,7 @@
202 isa = PBXProject; 202 isa = PBXProject;
203 attributes = { 203 attributes = {
204 LastSwiftUpdateCheck = 0920; 204 LastSwiftUpdateCheck = 0920;
205 - LastUpgradeCheck = 1300; 205 + LastUpgradeCheck = 1430;
206 ORGANIZATIONNAME = ""; 206 ORGANIZATIONNAME = "";
207 TargetAttributes = { 207 TargetAttributes = {
208 33CC10EC2044A3C60003C045 = { 208 33CC10EC2044A3C60003C045 = {
1 <?xml version="1.0" encoding="UTF-8"?> 1 <?xml version="1.0" encoding="UTF-8"?>
2 <Scheme 2 <Scheme
3 - LastUpgradeVersion = "1300" 3 + LastUpgradeVersion = "1430"
4 version = "1.3"> 4 version = "1.3">
5 <BuildAction 5 <BuildAction
6 parallelizeBuildables = "YES" 6 parallelizeBuildables = "YES"