Navaron Bracke

update barcode scanner window sample

@@ -16,16 +16,75 @@ class BarcodeScannerWithScanWindow extends StatefulWidget { @@ -16,16 +16,75 @@ class BarcodeScannerWithScanWindow extends StatefulWidget {
16 16
17 class _BarcodeScannerWithScanWindowState 17 class _BarcodeScannerWithScanWindowState
18 extends State<BarcodeScannerWithScanWindow> { 18 extends State<BarcodeScannerWithScanWindow> {
19 - late MobileScannerController controller = MobileScannerController();  
20 - Barcode? barcode;  
21 - BarcodeCapture? capture; 19 + final MobileScannerController controller = MobileScannerController();
22 20
23 - Future<void> onDetect(BarcodeCapture barcode) async {  
24 - capture = barcode;  
25 - setState(() => this.barcode = barcode.barcodes.first); 21 + @override
  22 + void initState() {
  23 + super.initState();
  24 +
  25 + controller.start();
26 } 26 }
27 27
28 - MobileScannerArguments? arguments; 28 + Widget _buildBarcodeOverlay() {
  29 + return ValueListenableBuilder(
  30 + valueListenable: controller,
  31 + builder: (context, value, child) {
  32 + // Not ready.
  33 + if (!value.isInitialized || !value.isRunning || value.error != null) {
  34 + return const SizedBox();
  35 + }
  36 +
  37 + return StreamBuilder<BarcodeCapture>(
  38 + stream: controller.barcodes,
  39 + builder: (context, snapshot) {
  40 + final BarcodeCapture? barcodeCapture = snapshot.data;
  41 +
  42 + // No barcode.
  43 + if (barcodeCapture == null || barcodeCapture.barcodes.isEmpty) {
  44 + return const SizedBox();
  45 + }
  46 +
  47 + final scannedBarcode = barcodeCapture.barcodes.first;
  48 +
  49 + // No barcode corners, or size, or no camera preview size.
  50 + if (scannedBarcode.corners.isEmpty ||
  51 + value.size.isEmpty ||
  52 + barcodeCapture.size.isEmpty) {
  53 + return const SizedBox();
  54 + }
  55 +
  56 + return CustomPaint(
  57 + painter: BarcodeOverlay(
  58 + barcodeCorners: scannedBarcode.corners,
  59 + barcodeSize: barcodeCapture.size,
  60 + boxFit: BoxFit.contain,
  61 + cameraPreviewSize: value.size,
  62 + ),
  63 + );
  64 + },
  65 + );
  66 + },
  67 + );
  68 + }
  69 +
  70 + Widget _buildScanWindow(Rect scanWindowRect) {
  71 + return ValueListenableBuilder(
  72 + valueListenable: controller,
  73 + builder: (context, value, child) {
  74 + // Not ready.
  75 + if (!value.isInitialized ||
  76 + !value.isRunning ||
  77 + value.error != null ||
  78 + value.size.isEmpty) {
  79 + return const SizedBox();
  80 + }
  81 +
  82 + return CustomPaint(
  83 + painter: ScannerOverlay(scanWindowRect),
  84 + );
  85 + },
  86 + );
  87 + }
29 88
30 @override 89 @override
31 Widget build(BuildContext context) { 90 Widget build(BuildContext context) {
@@ -34,77 +93,69 @@ class _BarcodeScannerWithScanWindowState @@ -34,77 +93,69 @@ class _BarcodeScannerWithScanWindowState
34 width: 200, 93 width: 200,
35 height: 200, 94 height: 200,
36 ); 95 );
  96 +
37 return Scaffold( 97 return Scaffold(
38 appBar: AppBar(title: const Text('With Scan window')), 98 appBar: AppBar(title: const Text('With Scan window')),
39 backgroundColor: Colors.black, 99 backgroundColor: Colors.black,
40 - body: Builder(  
41 - builder: (context) {  
42 - return Stack(  
43 - fit: StackFit.expand,  
44 - children: [  
45 - MobileScanner(  
46 - fit: BoxFit.contain,  
47 - scanWindow: scanWindow,  
48 - controller: controller,  
49 - onScannerStarted: (arguments) {  
50 - setState(() {  
51 - this.arguments = arguments;  
52 - });  
53 - },  
54 - errorBuilder: (context, error, child) {  
55 - return ScannerErrorWidget(error: error); 100 + body: Stack(
  101 + fit: StackFit.expand,
  102 + children: [
  103 + MobileScanner(
  104 + fit: BoxFit.contain,
  105 + scanWindow: scanWindow,
  106 + controller: controller,
  107 + errorBuilder: (context, error, child) {
  108 + return ScannerErrorWidget(error: error);
  109 + },
  110 + ),
  111 + _buildBarcodeOverlay(),
  112 + _buildScanWindow(scanWindow),
  113 + Align(
  114 + alignment: Alignment.bottomCenter,
  115 + child: Container(
  116 + alignment: Alignment.center,
  117 + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
  118 + height: 100,
  119 + color: Colors.black.withOpacity(0.4),
  120 + child: StreamBuilder<BarcodeCapture>(
  121 + stream: controller.barcodes,
  122 + builder: (context, snapshot) {
  123 + final barcodeCapture = snapshot.data;
  124 +
  125 + String displayValue = 'Scan something!';
  126 +
  127 + if (barcodeCapture != null &&
  128 + barcodeCapture.barcodes.isNotEmpty) {
  129 + final String? value =
  130 + barcodeCapture.barcodes.first.displayValue;
  131 +
  132 + if (value != null) {
  133 + displayValue = value;
  134 + }
  135 + }
  136 +
  137 + return Text(
  138 + displayValue,
  139 + overflow: TextOverflow.fade,
  140 + style: Theme.of(context)
  141 + .textTheme
  142 + .headlineMedium!
  143 + .copyWith(color: Colors.white),
  144 + );
56 }, 145 },
57 - onDetect: onDetect,  
58 - ),  
59 - if (barcode != null &&  
60 - barcode?.corners != null &&  
61 - arguments != null)  
62 - CustomPaint(  
63 - painter: BarcodeOverlay(  
64 - barcode: barcode!,  
65 - arguments: arguments!,  
66 - boxFit: BoxFit.contain,  
67 - capture: capture!,  
68 - ),  
69 - ),  
70 - CustomPaint(  
71 - painter: ScannerOverlay(scanWindow),  
72 - ),  
73 - Align(  
74 - alignment: Alignment.bottomCenter,  
75 - child: Container(  
76 - alignment: Alignment.bottomCenter,  
77 - height: 100,  
78 - color: Colors.black.withOpacity(0.4),  
79 - child: Row(  
80 - mainAxisAlignment: MainAxisAlignment.spaceEvenly,  
81 - children: [  
82 - Center(  
83 - child: SizedBox(  
84 - width: MediaQuery.of(context).size.width - 120,  
85 - height: 50,  
86 - child: FittedBox(  
87 - child: Text(  
88 - barcode?.displayValue ?? 'Scan something!',  
89 - overflow: TextOverflow.fade,  
90 - style: Theme.of(context)  
91 - .textTheme  
92 - .headlineMedium!  
93 - .copyWith(color: Colors.white),  
94 - ),  
95 - ),  
96 - ),  
97 - ),  
98 - ],  
99 - ),  
100 - ),  
101 ), 146 ),
102 - ],  
103 - );  
104 - }, 147 + ),
  148 + ),
  149 + ],
105 ), 150 ),
106 ); 151 );
107 } 152 }
  153 +
  154 + @override
  155 + Future<void> dispose() async {
  156 + await controller.dispose();
  157 + super.dispose();
  158 + }
108 } 159 }
109 160
110 class ScannerOverlay extends CustomPainter { 161 class ScannerOverlay extends CustomPainter {
@@ -114,6 +165,8 @@ class ScannerOverlay extends CustomPainter { @@ -114,6 +165,8 @@ class ScannerOverlay extends CustomPainter {
114 165
115 @override 166 @override
116 void paint(Canvas canvas, Size size) { 167 void paint(Canvas canvas, Size size) {
  168 + // TODO: use `Offset.zero & size` instead of Rect.largest
  169 + // we need to pass the size to the custom paint widget
117 final backgroundPath = Path()..addRect(Rect.largest); 170 final backgroundPath = Path()..addRect(Rect.largest);
118 final cutoutPath = Path()..addRect(scanWindow); 171 final cutoutPath = Path()..addRect(scanWindow);
119 172
@@ -151,7 +204,9 @@ class BarcodeOverlay extends CustomPainter { @@ -151,7 +204,9 @@ class BarcodeOverlay extends CustomPainter {
151 204
152 @override 205 @override
153 void paint(Canvas canvas, Size size) { 206 void paint(Canvas canvas, Size size) {
154 - if (barcodeCorners.isEmpty) { 207 + if (barcodeCorners.isEmpty ||
  208 + barcodeSize.isEmpty ||
  209 + cameraPreviewSize.isEmpty) {
155 return; 210 return;
156 } 211 }
157 212