Julian Steenbakker
Committed by GitHub

Merge pull request #53 from juliansteenbakker/example-app

bug: fixed pop() not working
@@ -25,132 +25,129 @@ class _BarcodeScannerWithControllerState @@ -25,132 +25,129 @@ class _BarcodeScannerWithControllerState
25 25
26 @override 26 @override
27 Widget build(BuildContext context) { 27 Widget build(BuildContext context) {
28 - return MaterialApp(  
29 - home: Scaffold(  
30 - backgroundColor: Colors.black,  
31 - body: Builder(builder: (context) {  
32 - return Stack(  
33 - children: [  
34 - MobileScanner(  
35 - controller: controller,  
36 - fit: BoxFit.contain,  
37 - // controller: MobileScannerController(  
38 - // torchEnabled: true,  
39 - // facing: CameraFacing.front,  
40 - // ),  
41 - onDetect: (barcode, args) {  
42 - if (this.barcode != barcode.rawValue) {  
43 - setState(() {  
44 - this.barcode = barcode.rawValue;  
45 - });  
46 - }  
47 - }),  
48 - Align( 28 + return 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 + allowDuplicates: false,
  37 + // controller: MobileScannerController(
  38 + // torchEnabled: true,
  39 + // facing: CameraFacing.front,
  40 + // ),
  41 + onDetect: (barcode, args) {
  42 + setState(() {
  43 + this.barcode = barcode.rawValue;
  44 + });
  45 + }),
  46 + Align(
  47 + alignment: Alignment.bottomCenter,
  48 + child: Container(
49 alignment: Alignment.bottomCenter, 49 alignment: Alignment.bottomCenter,
50 - child: Container(  
51 - alignment: Alignment.bottomCenter,  
52 - height: 100,  
53 - color: Colors.black.withOpacity(0.4),  
54 - child: Row(  
55 - crossAxisAlignment: CrossAxisAlignment.center,  
56 - mainAxisAlignment: MainAxisAlignment.spaceEvenly,  
57 - children: [  
58 - IconButton( 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(
59 color: Colors.white, 75 color: Colors.white,
60 - icon: ValueListenableBuilder(  
61 - valueListenable: controller.torchState,  
62 - builder: (context, state, child) {  
63 - switch (state as TorchState) {  
64 - case TorchState.off:  
65 - return const Icon(Icons.flash_off,  
66 - color: Colors.grey);  
67 - case TorchState.on:  
68 - return const Icon(Icons.flash_on,  
69 - color: Colors.yellow);  
70 - }  
71 - },  
72 - ), 76 + icon: isStarted
  77 + ? const Icon(Icons.stop)
  78 + : const Icon(Icons.play_arrow),
73 iconSize: 32.0, 79 iconSize: 32.0,
74 - onPressed: () => controller.toggleTorch(),  
75 - ),  
76 - IconButton(  
77 - color: Colors.white,  
78 - icon: isStarted  
79 - ? const Icon(Icons.stop)  
80 - : const Icon(Icons.play_arrow),  
81 - iconSize: 32.0,  
82 - onPressed: () => setState(() {  
83 - isStarted  
84 - ? controller.stop()  
85 - : controller.start();  
86 - isStarted = !isStarted;  
87 - })),  
88 - Center(  
89 - child: SizedBox(  
90 - width: MediaQuery.of(context).size.width - 200,  
91 - height: 50,  
92 - child: FittedBox(  
93 - child: Text(  
94 - barcode ?? 'Scan something!',  
95 - overflow: TextOverflow.fade,  
96 - style: Theme.of(context)  
97 - .textTheme  
98 - .headline4!  
99 - .copyWith(color: Colors.white),  
100 - ), 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 - 200,
  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),
101 ), 98 ),
102 ), 99 ),
103 ), 100 ),
104 - IconButton(  
105 - color: Colors.white,  
106 - icon: ValueListenableBuilder(  
107 - valueListenable: controller.cameraFacingState,  
108 - builder: (context, state, child) {  
109 - switch (state as CameraFacing) {  
110 - case CameraFacing.front:  
111 - return const Icon(Icons.camera_front);  
112 - case CameraFacing.back:  
113 - return const Icon(Icons.camera_rear);  
114 - }  
115 - },  
116 - ),  
117 - iconSize: 32.0,  
118 - onPressed: () => controller.switchCamera(),  
119 - ),  
120 - IconButton(  
121 - color: Colors.white,  
122 - icon: const Icon(Icons.image),  
123 - iconSize: 32.0,  
124 - onPressed: () async {  
125 - final ImagePicker _picker = ImagePicker();  
126 - // Pick an image  
127 - final XFile? image = await _picker.pickImage(  
128 - source: ImageSource.gallery);  
129 - if (image != null) {  
130 - if (await controller.analyzeImage(image.path)) {  
131 - ScaffoldMessenger.of(context)  
132 - .showSnackBar(const SnackBar(  
133 - content: Text('Barcode found!'),  
134 - backgroundColor: Colors.green,  
135 - ));  
136 - } else {  
137 - ScaffoldMessenger.of(context)  
138 - .showSnackBar(const SnackBar(  
139 - content: Text('No barcode found!'),  
140 - backgroundColor: Colors.red,  
141 - ));  
142 - } 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);
143 } 112 }
144 }, 113 },
145 ), 114 ),
146 - ],  
147 - ), 115 + iconSize: 32.0,
  116 + onPressed: () => controller.switchCamera(),
  117 + ),
  118 + IconButton(
  119 + color: Colors.white,
  120 + icon: const Icon(Icons.image),
  121 + iconSize: 32.0,
  122 + onPressed: () async {
  123 + final ImagePicker _picker = ImagePicker();
  124 + // Pick an image
  125 + final XFile? image = await _picker.pickImage(
  126 + source: ImageSource.gallery);
  127 + if (image != null) {
  128 + if (await controller.analyzeImage(image.path)) {
  129 + ScaffoldMessenger.of(context)
  130 + .showSnackBar(const SnackBar(
  131 + content: Text('Barcode found!'),
  132 + backgroundColor: Colors.green,
  133 + ));
  134 + } else {
  135 + ScaffoldMessenger.of(context)
  136 + .showSnackBar(const SnackBar(
  137 + content: Text('No barcode found!'),
  138 + backgroundColor: Colors.red,
  139 + ));
  140 + }
  141 + }
  142 + },
  143 + ),
  144 + ],
148 ), 145 ),
149 ), 146 ),
150 - ],  
151 - );  
152 - }),  
153 - ), 147 + ),
  148 + ],
  149 + );
  150 + }),
154 ); 151 );
155 } 152 }
156 } 153 }
@@ -16,55 +16,52 @@ class _BarcodeScannerWithoutControllerState @@ -16,55 +16,52 @@ class _BarcodeScannerWithoutControllerState
16 16
17 @override 17 @override
18 Widget build(BuildContext context) { 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( 19 + return Scaffold(
  20 + backgroundColor: Colors.black,
  21 + body: Builder(builder: (context) {
  22 + return Stack(
  23 + children: [
  24 + MobileScanner(
  25 + fit: BoxFit.contain,
  26 + allowDuplicates: false,
  27 + onDetect: (barcode, args) {
  28 + setState(() {
  29 + this.barcode = barcode.rawValue;
  30 + });
  31 + }),
  32 + Align(
  33 + alignment: Alignment.bottomCenter,
  34 + child: Container(
35 alignment: Alignment.bottomCenter, 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 - ), 36 + height: 100,
  37 + color: Colors.black.withOpacity(0.4),
  38 + child: Row(
  39 + crossAxisAlignment: CrossAxisAlignment.center,
  40 + mainAxisAlignment: MainAxisAlignment.spaceEvenly,
  41 + children: [
  42 + Center(
  43 + child: SizedBox(
  44 + width: MediaQuery.of(context).size.width - 120,
  45 + height: 50,
  46 + child: FittedBox(
  47 + child: Text(
  48 + barcode ?? 'Scan something!',
  49 + overflow: TextOverflow.fade,
  50 + style: Theme.of(context)
  51 + .textTheme
  52 + .headline4!
  53 + .copyWith(color: Colors.white),
57 ), 54 ),
58 ), 55 ),
59 ), 56 ),
60 - ],  
61 - ), 57 + ),
  58 + ],
62 ), 59 ),
63 ), 60 ),
64 - ],  
65 - );  
66 - }),  
67 - ), 61 + ),
  62 + ],
  63 + );
  64 + }),
68 ); 65 );
69 } 66 }
70 } 67 }
@@ -24,9 +24,16 @@ class MobileScanner extends StatefulWidget { @@ -24,9 +24,16 @@ class MobileScanner extends StatefulWidget {
24 /// Handles how the widget should fit the screen. 24 /// Handles how the widget should fit the screen.
25 final BoxFit fit; 25 final BoxFit fit;
26 26
  27 + /// Set to false if you don't want duplicate scans.
  28 + final bool allowDuplicates;
  29 +
27 /// Create a [MobileScanner] with a [controller], the [controller] must has been initialized. 30 /// Create a [MobileScanner] with a [controller], the [controller] must has been initialized.
28 const MobileScanner( 31 const MobileScanner(
29 - {Key? key, this.onDetect, this.controller, this.fit = BoxFit.cover}) 32 + {Key? key,
  33 + this.onDetect,
  34 + this.controller,
  35 + this.fit = BoxFit.cover,
  36 + this.allowDuplicates = true})
30 : super(key: key); 37 : super(key: key);
31 38
32 @override 39 @override
@@ -58,6 +65,8 @@ class _MobileScannerState extends State<MobileScanner> @@ -58,6 +65,8 @@ class _MobileScannerState extends State<MobileScanner>
58 } 65 }
59 } 66 }
60 67
  68 + String? lastScanned;
  69 +
61 @override 70 @override
62 Widget build(BuildContext context) { 71 Widget build(BuildContext context) {
63 return LayoutBuilder(builder: (context, BoxConstraints constraints) { 72 return LayoutBuilder(builder: (context, BoxConstraints constraints) {
@@ -68,8 +77,16 @@ class _MobileScannerState extends State<MobileScanner> @@ -68,8 +77,16 @@ class _MobileScannerState extends State<MobileScanner>
68 if (value == null) { 77 if (value == null) {
69 return Container(color: Colors.black); 78 return Container(color: Colors.black);
70 } else { 79 } else {
71 - controller.barcodes.listen(  
72 - (a) => widget.onDetect!(a, value as MobileScannerArguments)); 80 + controller.barcodes.listen((barcode) {
  81 + if (!widget.allowDuplicates) {
  82 + if (lastScanned != barcode.rawValue) {
  83 + lastScanned = barcode.rawValue;
  84 + widget.onDetect!(barcode, value as MobileScannerArguments);
  85 + }
  86 + } else {
  87 + widget.onDetect!(barcode, value as MobileScannerArguments);
  88 + }
  89 + });
73 return ClipRect( 90 return ClipRect(
74 child: SizedBox( 91 child: SizedBox(
75 width: MediaQuery.of(context).size.width, 92 width: MediaQuery.of(context).size.width,