Navaron Bracke

clean up the mobile_scanner_overlay sample

@@ -64,7 +64,9 @@ class MyHome extends StatelessWidget { @@ -64,7 +64,9 @@ class MyHome extends StatelessWidget {
64 ), 64 ),
65 ); 65 );
66 }, 66 },
67 - child: const Text('MobileScanner with Controller (returning image)'), 67 + child: const Text(
  68 + 'MobileScanner with Controller (returning image)',
  69 + ),
68 ), 70 ),
69 ElevatedButton( 71 ElevatedButton(
70 onPressed: () { 72 onPressed: () {
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 +import 'package:mobile_scanner_example/scanned_barcode_label.dart';
  4 +import 'package:mobile_scanner_example/scanner_button_widgets.dart';
3 import 'package:mobile_scanner_example/scanner_error_widget.dart'; 5 import 'package:mobile_scanner_example/scanner_error_widget.dart';
4 6
5 class BarcodeScannerWithOverlay extends StatefulWidget { 7 class BarcodeScannerWithOverlay extends StatefulWidget {
6 @override 8 @override
7 - _BarcodeScannerWithOverlayState createState() =>  
8 - _BarcodeScannerWithOverlayState(); 9 + _BarcodeScannerWithOverlayState createState() => _BarcodeScannerWithOverlayState();
9 } 10 }
10 11
11 class _BarcodeScannerWithOverlayState extends State<BarcodeScannerWithOverlay> { 12 class _BarcodeScannerWithOverlayState extends State<BarcodeScannerWithOverlay> {
12 - String overlayText = "Please scan QR Code";  
13 - bool camStarted = false;  
14 -  
15 final MobileScannerController controller = MobileScannerController( 13 final MobileScannerController controller = MobileScannerController(
16 formats: const [BarcodeFormat.qrCode], 14 formats: const [BarcodeFormat.qrCode],
17 - autoStart: false,  
18 ); 15 );
19 16
20 @override 17 @override
21 - void dispose() {  
22 - controller.dispose();  
23 - super.dispose();  
24 - }  
25 -  
26 - void startCamera() {  
27 - if (camStarted) {  
28 - return;  
29 - }  
30 -  
31 - controller.start().then((_) {  
32 - if (mounted) {  
33 - setState(() {  
34 - camStarted = true;  
35 - });  
36 - }  
37 - }).catchError((Object error, StackTrace stackTrace) {  
38 - if (mounted) {  
39 - ScaffoldMessenger.of(context).showSnackBar(  
40 - SnackBar(  
41 - content: Text('Something went wrong! $error'),  
42 - backgroundColor: Colors.red,  
43 - ),  
44 - );  
45 - }  
46 - });  
47 - }  
48 -  
49 - void onBarcodeDetect(BarcodeCapture barcodeCapture) {  
50 - final barcode = barcodeCapture.barcodes.last;  
51 - setState(() {  
52 - overlayText = barcodeCapture.barcodes.last.displayValue ??  
53 - barcode.rawValue ??  
54 - 'Barcode has no displayable value';  
55 - }); 18 + void initState() {
  19 + super.initState();
  20 + controller.start();
56 } 21 }
57 22
58 @override 23 @override
@@ -64,119 +29,84 @@ class _BarcodeScannerWithOverlayState extends State<BarcodeScannerWithOverlay> { @@ -64,119 +29,84 @@ class _BarcodeScannerWithOverlayState extends State<BarcodeScannerWithOverlay> {
64 ); 29 );
65 30
66 return Scaffold( 31 return Scaffold(
  32 + backgroundColor: Colors.black,
67 appBar: AppBar( 33 appBar: AppBar(
68 title: const Text('Scanner with Overlay Example app'), 34 title: const Text('Scanner with Overlay Example app'),
69 ), 35 ),
70 - body: Center(  
71 - child: Column(  
72 - mainAxisAlignment: MainAxisAlignment.center,  
73 - children: <Widget>[  
74 - Expanded(  
75 - child: camStarted  
76 - ? Stack( 36 + body: Stack(
77 fit: StackFit.expand, 37 fit: StackFit.expand,
78 children: [ 38 children: [
79 Center( 39 Center(
80 child: MobileScanner( 40 child: MobileScanner(
81 fit: BoxFit.contain, 41 fit: BoxFit.contain,
82 - onDetect: onBarcodeDetect,  
83 - overlay: Padding(  
84 - padding: const EdgeInsets.all(16.0),  
85 - child: Align(  
86 - alignment: Alignment.bottomCenter,  
87 - child: Opacity(  
88 - opacity: 0.7,  
89 - child: Text(  
90 - overlayText,  
91 - style: const TextStyle(  
92 - backgroundColor: Colors.black26,  
93 - color: Colors.white,  
94 - fontWeight: FontWeight.bold,  
95 - fontSize: 24,  
96 - overflow: TextOverflow.ellipsis,  
97 - ),  
98 - maxLines: 1,  
99 - ),  
100 - ),  
101 - ),  
102 - ),  
103 controller: controller, 42 controller: controller,
104 scanWindow: scanWindow, 43 scanWindow: scanWindow,
105 errorBuilder: (context, error, child) { 44 errorBuilder: (context, error, child) {
106 return ScannerErrorWidget(error: error); 45 return ScannerErrorWidget(error: error);
107 }, 46 },
108 - ),  
109 - ),  
110 - CustomPaint(  
111 - painter: ScannerOverlay(scanWindow),  
112 - ),  
113 - Padding( 47 + overlayBuilder: (context, constraints) {
  48 + return Padding(
114 padding: const EdgeInsets.all(16.0), 49 padding: const EdgeInsets.all(16.0),
115 child: Align( 50 child: Align(
116 alignment: Alignment.bottomCenter, 51 alignment: Alignment.bottomCenter,
117 - child: Row(  
118 - mainAxisAlignment: MainAxisAlignment.spaceEvenly,  
119 - children: [  
120 - ValueListenableBuilder<TorchState>(  
121 - valueListenable: controller.torchState,  
122 - builder: (context, value, child) {  
123 - final Color iconColor;  
124 -  
125 - switch (value) {  
126 - case TorchState.off:  
127 - iconColor = Colors.black;  
128 - case TorchState.on:  
129 - iconColor = Colors.yellow;  
130 - }  
131 -  
132 - return IconButton(  
133 - onPressed: () => controller.toggleTorch(),  
134 - icon: Icon(  
135 - Icons.flashlight_on,  
136 - color: iconColor, 52 + child: ScannedBarcodeLabel(barcodes: controller.barcodes),
137 ), 53 ),
138 ); 54 );
139 }, 55 },
140 ), 56 ),
141 - IconButton(  
142 - onPressed: () => controller.switchCamera(),  
143 - icon: const Icon(  
144 - Icons.cameraswitch_rounded,  
145 - color: Colors.white,  
146 ), 57 ),
  58 + ValueListenableBuilder(
  59 + valueListenable: controller,
  60 + builder: (context, value, child) {
  61 + if (!value.isInitialized || !value.isRunning || value.error != null) {
  62 + return const SizedBox();
  63 + }
  64 +
  65 + return CustomPaint(
  66 + painter: ScannerOverlay(scanWindow: scanWindow),
  67 + );
  68 + },
147 ), 69 ),
  70 + Align(
  71 + alignment: Alignment.bottomCenter,
  72 + child: Padding(
  73 + padding: const EdgeInsets.all(16.0),
  74 + child: Row(
  75 + mainAxisAlignment: MainAxisAlignment.spaceEvenly,
  76 + children: [
  77 + ToggleFlashlightButton(controller: controller),
  78 + SwitchCameraButton(controller: controller),
148 ], 79 ],
149 ), 80 ),
150 ), 81 ),
151 ), 82 ),
152 ], 83 ],
153 - )  
154 - : const Center(  
155 - child: Text("Tap on Camera to activate QR Scanner"),  
156 - ),  
157 - ),  
158 - ],  
159 - ),  
160 - ),  
161 - floatingActionButton: camStarted  
162 - ? null  
163 - : FloatingActionButton(  
164 - onPressed: startCamera,  
165 - child: const Icon(Icons.camera_alt),  
166 ), 84 ),
167 ); 85 );
168 } 86 }
  87 +
  88 + @override
  89 + Future<void> dispose() async {
  90 + await controller.dispose();
  91 + super.dispose();
  92 + }
169 } 93 }
170 94
171 class ScannerOverlay extends CustomPainter { 95 class ScannerOverlay extends CustomPainter {
172 - ScannerOverlay(this.scanWindow); 96 + const ScannerOverlay({
  97 + required this.scanWindow,
  98 + this.borderRadius = 12.0,
  99 + });
173 100
174 final Rect scanWindow; 101 final Rect scanWindow;
175 - final double borderRadius = 12.0; 102 + final double borderRadius;
176 103
177 @override 104 @override
178 void paint(Canvas canvas, Size size) { 105 void paint(Canvas canvas, Size size) {
  106 + // TODO: use `Offset.zero & size` instead of Rect.largest
  107 + // we need to pass the size to the custom paint widget
179 final backgroundPath = Path()..addRect(Rect.largest); 108 final backgroundPath = Path()..addRect(Rect.largest);
  109 +
180 final cutoutPath = Path() 110 final cutoutPath = Path()
181 ..addRRect( 111 ..addRRect(
182 RRect.fromRectAndCorners( 112 RRect.fromRectAndCorners(
@@ -199,14 +129,11 @@ class ScannerOverlay extends CustomPainter { @@ -199,14 +129,11 @@ class ScannerOverlay extends CustomPainter {
199 cutoutPath, 129 cutoutPath,
200 ); 130 );
201 131
202 - // Create a Paint object for the white border  
203 final borderPaint = Paint() 132 final borderPaint = Paint()
204 ..color = Colors.white 133 ..color = Colors.white
205 ..style = PaintingStyle.stroke 134 ..style = PaintingStyle.stroke
206 - ..strokeWidth = 4.0; // Adjust the border width as needed 135 + ..strokeWidth = 4.0;
207 136
208 - // Calculate the border rectangle with rounded corners  
209 -// Adjust the radius as needed  
210 final borderRect = RRect.fromRectAndCorners( 137 final borderRect = RRect.fromRectAndCorners(
211 scanWindow, 138 scanWindow,
212 topLeft: Radius.circular(borderRadius), 139 topLeft: Radius.circular(borderRadius),
@@ -215,13 +142,15 @@ class ScannerOverlay extends CustomPainter { @@ -215,13 +142,15 @@ class ScannerOverlay extends CustomPainter {
215 bottomRight: Radius.circular(borderRadius), 142 bottomRight: Radius.circular(borderRadius),
216 ); 143 );
217 144
218 - // Draw the white border 145 + // First, draw the background,
  146 + // with a cutout area that is a bit larger than the scan window.
  147 + // Finally, draw the scan window itself.
219 canvas.drawPath(backgroundWithCutout, backgroundPaint); 148 canvas.drawPath(backgroundWithCutout, backgroundPaint);
220 canvas.drawRRect(borderRect, borderPaint); 149 canvas.drawRRect(borderRect, borderPaint);
221 } 150 }
222 151
223 @override 152 @override
224 - bool shouldRepaint(covariant CustomPainter oldDelegate) {  
225 - return false; 153 + bool shouldRepaint(ScannerOverlay oldDelegate) {
  154 + return scanWindow != oldDelegate.scanWindow || borderRadius != oldDelegate.borderRadius;
226 } 155 }
227 } 156 }