• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

inventree / inventree-app / 18962689941

31 Oct 2025 04:26AM UTC coverage: 1.474%. Remained the same
18962689941

push

github

web-flow
Possible fix for barcode scanner issues (#702)

* Possible fix for barcode scanner issues

Ref: https://github.com/juliansteenbakker/mobile_scanner/issues/1454#issuecomment-3329405748

* Bump release notes

* Bump app revision

0 of 1 new or added line in 1 file covered. (0.0%)

1 existing line in 1 file now uncovered.

768 of 52106 relevant lines covered (1.47%)

0.05 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/lib/barcode/camera_controller.dart
1
import "dart:math";
2

3
import "package:camera/camera.dart";
4
import "package:flutter/material.dart";
5
import "package:flutter_speed_dial/flutter_speed_dial.dart";
6
import "package:flutter_tabler_icons/flutter_tabler_icons.dart";
7
import "package:inventree/app_colors.dart";
8
import "package:inventree/inventree/sentry.dart";
9
import "package:inventree/preferences.dart";
10
import "package:inventree/widget/snacks.dart";
11
import "package:mobile_scanner/mobile_scanner.dart";
12
import "package:one_context/one_context.dart";
13
import "package:wakelock_plus/wakelock_plus.dart";
14

15
import "package:inventree/l10.dart";
16

17
import "package:inventree/barcode/handler.dart";
18
import "package:inventree/barcode/controller.dart";
19

20
/*
21
 * Barcode controller which uses the device's camera to scan barcodes.
22
 * Under the hood it uses the qr_code_scanner package.
23
 */
24
class CameraBarcodeController extends InvenTreeBarcodeController {
25
  const CameraBarcodeController(BarcodeHandler handler, {Key? key})
×
26
    : super(handler, key: key);
×
27

28
  @override
×
29
  State<StatefulWidget> createState() => _CameraBarcodeControllerState();
×
30
}
31

32
class _CameraBarcodeControllerState extends InvenTreeBarcodeControllerState {
33
  _CameraBarcodeControllerState() : super();
×
34

35
  bool flash_status = false;
36

37
  int scan_delay = 500;
38
  bool single_scanning = false;
39
  bool scanning_paused = false;
40
  bool multiple_barcodes = false;
41

42
  String scanned_code = "";
43

44
  final MobileScannerController controller = MobileScannerController(
45
    autoZoom: true,
46
  );
47

48
  @override
×
49
  void initState() {
50
    super.initState();
×
51
    _loadSettings();
×
52
    WakelockPlus.enable();
×
53
  }
54

55
  @override
×
56
  void dispose() {
57
    super.dispose();
×
NEW
58
    controller.dispose();
×
UNCOV
59
    WakelockPlus.disable();
×
60
  }
61

62
  /*
63
   * Load the barcode scanning settings
64
   */
65
  Future<void> _loadSettings() async {
×
66
    bool _single = await InvenTreeSettingsManager().getBool(
×
67
      INV_BARCODE_SCAN_SINGLE,
68
      false,
69
    );
70

71
    int _delay =
72
        await InvenTreeSettingsManager().getValue(INV_BARCODE_SCAN_DELAY, 500)
×
73
            as int;
74

75
    if (mounted) {
×
76
      setState(() {
×
77
        scan_delay = _delay;
×
78
        single_scanning = _single;
×
79
        scanning_paused = false;
×
80
      });
81
    }
82
  }
83

84
  @override
×
85
  Future<void> pauseScan() async {
86
    if (mounted) {
×
87
      setState(() {
×
88
        scanning_paused = true;
×
89
      });
90
    }
91
  }
92

93
  @override
×
94
  Future<void> resumeScan() async {
95
    controller.start();
×
96

97
    if (mounted) {
×
98
      setState(() {
×
99
        scanning_paused = false;
×
100
      });
101
    }
102
  }
103

104
  /*
105
   * Callback function when a barcode is scanned
106
   */
107
  Future<void> onScanSuccess(BarcodeCapture result) async {
×
108
    if (!mounted || scanning_paused) {
×
109
      return;
110
    }
111

112
    // TODO: Display outline of barcodes on the screen?
113

114
    if (result.barcodes.isEmpty) {
×
115
      setState(() {
×
116
        multiple_barcodes = false;
×
117
      });
118
    } else if (result.barcodes.length > 1) {
×
119
      setState(() {
×
120
        multiple_barcodes = true;
×
121
      });
122
      return;
123
    } else {
124
      setState(() {
×
125
        multiple_barcodes = false;
×
126
      });
127
    }
128

129
    String barcode = result.barcodes.first.rawValue ?? "";
×
130

131
    if (barcode.isEmpty) {
×
132
      // TODO: Error message "empty barcode"
133
      return;
134
    }
135

136
    setState(() {
×
137
      scanned_code = barcode;
×
138
    });
139

140
    pauseScan();
×
141

142
    await handleBarcodeData(barcode).then((_) {
×
143
      if (!single_scanning && mounted) {
×
144
        resumeScan();
×
145
      }
146
    });
147

148
    resumeScan();
×
149

150
    if (mounted) {
×
151
      setState(() {
×
152
        scanned_code = "";
×
153
        multiple_barcodes = false;
×
154
      });
155
    }
156
  }
157

158
  void onControllerCreated(CameraController? controller, Exception? error) {
×
159
    if (error != null) {
160
      sentryReportError(
×
161
        "CameraBarcodeController.onControllerCreated",
162
        error,
163
        null,
164
      );
165
    }
166

167
    if (controller == null) {
168
      showSnackIcon(
×
169
        L10().cameraCreationError,
×
170
        icon: TablerIcons.camera_x,
171
        success: false,
172
      );
173

174
      if (OneContext.hasContext) {
×
175
        Navigator.pop(OneContext().context!);
×
176
      }
177
    }
178
  }
179

180
  Widget BarcodeOverlay(BuildContext context) {
×
181
    final Size screenSize = MediaQuery.of(context).size;
×
182
    final double width = screenSize.width;
×
183
    final double height = screenSize.height;
×
184

185
    final double D = min(width, height) * 0.8;
×
186

187
    // Color for the barcode scan?
188
    Color overlayColor = COLOR_ACTION;
×
189

190
    if (multiple_barcodes) {
×
191
      overlayColor = COLOR_DANGER;
192
    } else if (scanned_code.isNotEmpty) {
×
193
      overlayColor = COLOR_SUCCESS;
194
    } else if (scanning_paused) {
×
195
      overlayColor = COLOR_WARNING;
196
    }
197

198
    return Stack(
×
199
      children: [
×
200
        Center(
×
201
          child: Container(
×
202
            width: D,
203
            height: D,
204
            decoration: BoxDecoration(
×
205
              border: Border.all(color: overlayColor, width: 4),
×
206
            ),
207
          ),
208
        ),
209
      ],
210
    );
211
  }
212

213
  /*
214
   * Build the barcode reader widget
215
   */
216
  Widget BarcodeReader(BuildContext context) {
×
217
    final Size screenSize = MediaQuery.of(context).size;
×
218
    final double width = screenSize.width;
×
219
    final double height = screenSize.height;
×
220

221
    final double D = min(width, height) * 0.8;
×
222

223
    return MobileScanner(
×
224
      controller: controller,
×
225
      overlayBuilder: (context, constraints) {
×
226
        return BarcodeOverlay(context);
×
227
      },
228
      scanWindow: Rect.fromCenter(
×
229
        center: Offset(width / 2, height / 2),
×
230
        width: D,
231
        height: D,
232
      ),
233
      onDetect: (result) {
×
234
        onScanSuccess(result);
×
235
      },
236
    );
237
  }
238

239
  Widget topCenterOverlay() {
×
240
    return SafeArea(
×
241
      child: Align(
×
242
        alignment: Alignment.topCenter,
243
        child: Padding(
×
244
          padding: EdgeInsets.only(left: 10, right: 10, top: 75, bottom: 10),
×
245
          child: Text(
×
246
            widget.handler.getOverlayText(context),
×
247
            style: TextStyle(
×
248
              color: Colors.white,
249
              fontSize: 16,
250
              fontWeight: FontWeight.bold,
251
            ),
252
          ),
253
        ),
254
      ),
255
    );
256
  }
257

258
  Widget bottomCenterOverlay() {
×
259
    String info_text = scanning_paused
×
260
        ? L10().barcodeScanPaused
×
261
        : L10().barcodeScanPause;
×
262

263
    String text = scanned_code.isNotEmpty ? scanned_code : info_text;
×
264

265
    if (text.length > 50) {
×
266
      text = text.substring(0, 50) + "...";
×
267
    }
268

269
    return SafeArea(
×
270
      child: Align(
×
271
        alignment: Alignment.bottomCenter,
272
        child: Padding(
×
273
          padding: EdgeInsets.only(left: 10, right: 10, top: 10, bottom: 75),
×
274
          child: Text(
×
275
            text,
276
            textAlign: TextAlign.center,
277
            style: TextStyle(
×
278
              color: Colors.white,
279
              fontSize: 16,
280
              fontWeight: FontWeight.bold,
281
            ),
282
          ),
283
        ),
284
      ),
285
    );
286
  }
287

288
  Widget? buildActions(BuildContext context) {
×
289
    List<SpeedDialChild> actions = [
×
290
      SpeedDialChild(
×
291
        child: Icon(flash_status ? TablerIcons.bulb_off : TablerIcons.bulb),
×
292
        label: L10().toggleTorch,
×
293
        onTap: () async {
×
294
          controller.toggleTorch();
×
295
          if (mounted) {
×
296
            setState(() {
×
297
              flash_status = !flash_status;
×
298
            });
299
          }
300
        },
301
      ),
302
      SpeedDialChild(
×
303
        child: Icon(TablerIcons.camera),
×
304
        label: L10().switchCamera,
×
305
        onTap: () async {
×
306
          controller.switchCamera();
×
307
        },
308
      ),
309
    ];
310

311
    return SpeedDial(icon: Icons.more_horiz, children: actions);
×
312
  }
313

314
  @override
×
315
  Widget build(BuildContext context) {
316
    return Scaffold(
×
317
      appBar: AppBar(
×
318
        backgroundColor: COLOR_APP_BAR,
319
        title: Text(L10().scanBarcode),
×
320
      ),
321
      floatingActionButton: buildActions(context),
×
322
      body: GestureDetector(
×
323
        onTap: () async {
×
324
          if (mounted) {
×
325
            setState(() {
×
326
              // Toggle the 'scan paused' state
327
              scanning_paused = !scanning_paused;
×
328
            });
329
          }
330
        },
331
        child: Stack(
×
332
          children: <Widget>[
×
333
            Column(children: [Expanded(child: BarcodeReader(context))]),
×
334
            topCenterOverlay(),
×
335
            bottomCenterOverlay(),
×
336
          ],
337
        ),
338
      ),
339
    );
340
  }
341
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc