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

inventree / inventree-app / 15837688195

23 Jun 2025 11:55PM UTC coverage: 1.586% (+0.02%) from 1.566%
15837688195

push

github

web-flow
Format Code and Add Format Checks to CI (#643)

* Remove unused lib/generated/i18n.dart

* Use `fvm dart format .`

* Add contributing guidelines

* Enforce dart format

* Add `dart format off` directive to generated files

133 of 2648 new or added lines in 82 files covered. (5.02%)

430 existing lines in 78 files now uncovered.

771 of 48628 relevant lines covered (1.59%)

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
import "dart:typed_data";
3

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

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

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

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

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

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

36
  bool flash_status = false;
37

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

43
  String scanned_code = "";
44

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

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

56
  @override
×
57
  void dispose() {
58
    super.dispose();
×
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
    Uint8List rawData = result.barcodes.first.rawBytes ?? Uint8List(0);
×
130

131
    String barcode;
132

133
    if (rawData.isNotEmpty) {
×
134
      final buffer = StringBuffer();
×
135

136
      for (int ii = 0; ii < rawData.length; ii++) {
×
137
        buffer.writeCharCode(rawData[ii]);
×
138
      }
139

140
      barcode = buffer.toString();
×
141

142
      print(barcode);
×
143
    } else {
144
      // Fall back to text value
145
      barcode = result.barcodes.first.rawValue ?? "";
×
146
    }
147

148
    if (barcode.isEmpty) {
×
149
      // TODO: Error message "empty barcode"
150
      return;
151
    }
152

153
    setState(() {
×
154
      scanned_code = barcode;
×
155
    });
156

157
    pauseScan();
×
158

159
    await handleBarcodeData(barcode).then((_) {
×
160
      if (!single_scanning && mounted) {
×
161
        resumeScan();
×
162
      }
163
    });
164

165
    resumeScan();
×
166

167
    if (mounted) {
×
168
      setState(() {
×
169
        scanned_code = "";
×
170
        multiple_barcodes = false;
×
171
      });
172
    }
173
  }
174

175
  void onControllerCreated(CameraController? controller, Exception? error) {
×
176
    if (error != null) {
177
      sentryReportError(
×
178
        "CameraBarcodeController.onControllerCreated",
179
        error,
180
        null,
181
      );
182
    }
183

184
    if (controller == null) {
185
      showSnackIcon(
×
186
        L10().cameraCreationError,
×
187
        icon: TablerIcons.camera_x,
188
        success: false,
189
      );
190

191
      if (OneContext.hasContext) {
×
192
        Navigator.pop(OneContext().context!);
×
193
      }
194
    }
195
  }
196

197
  Widget BarcodeOverlay(BuildContext context) {
×
198
    final Size screenSize = MediaQuery.of(context).size;
×
199
    final double width = screenSize.width;
×
200
    final double height = screenSize.height;
×
201

202
    final double D = min(width, height) * 0.8;
×
203

204
    // Color for the barcode scan?
205
    Color overlayColor = COLOR_ACTION;
×
206

207
    if (multiple_barcodes) {
×
208
      overlayColor = COLOR_DANGER;
209
    } else if (scanned_code.isNotEmpty) {
×
210
      overlayColor = COLOR_SUCCESS;
211
    } else if (scanning_paused) {
×
212
      overlayColor = COLOR_WARNING;
213
    }
214

215
    return Stack(
×
216
      children: [
×
217
        Center(
×
218
          child: Container(
×
219
            width: D,
220
            height: D,
221
            decoration: BoxDecoration(
×
222
              border: Border.all(color: overlayColor, width: 4),
×
223
            ),
224
          ),
225
        ),
226
      ],
227
    );
228
  }
229

230
  /*
231
   * Build the barcode reader widget
232
   */
233
  Widget BarcodeReader(BuildContext context) {
×
234
    final Size screenSize = MediaQuery.of(context).size;
×
235
    final double width = screenSize.width;
×
236
    final double height = screenSize.height;
×
237

238
    final double D = min(width, height) * 0.8;
×
239

240
    return MobileScanner(
×
241
      controller: controller,
×
242
      overlayBuilder: (context, constraints) {
×
243
        return BarcodeOverlay(context);
×
244
      },
245
      scanWindow: Rect.fromCenter(
×
246
        center: Offset(width / 2, height / 2),
×
247
        width: D,
248
        height: D,
249
      ),
250
      onDetect: (result) {
×
251
        onScanSuccess(result);
×
252
      },
253
    );
254
  }
255

256
  Widget topCenterOverlay() {
×
257
    return SafeArea(
×
258
      child: Align(
×
259
        alignment: Alignment.topCenter,
260
        child: Padding(
×
261
          padding: EdgeInsets.only(left: 10, right: 10, top: 75, bottom: 10),
×
262
          child: Text(
×
263
            widget.handler.getOverlayText(context),
×
264
            style: TextStyle(
×
265
              color: Colors.white,
266
              fontSize: 16,
267
              fontWeight: FontWeight.bold,
268
            ),
269
          ),
270
        ),
271
      ),
272
    );
273
  }
274

275
  Widget bottomCenterOverlay() {
×
NEW
276
    String info_text = scanning_paused
×
NEW
277
        ? L10().barcodeScanPaused
×
NEW
278
        : L10().barcodeScanPause;
×
279

280
    String text = scanned_code.isNotEmpty ? scanned_code : info_text;
×
281

282
    if (text.length > 50) {
×
283
      text = text.substring(0, 50) + "...";
×
284
    }
285

286
    return SafeArea(
×
287
      child: Align(
×
288
        alignment: Alignment.bottomCenter,
289
        child: Padding(
×
290
          padding: EdgeInsets.only(left: 10, right: 10, top: 10, bottom: 75),
×
291
          child: Text(
×
292
            text,
293
            textAlign: TextAlign.center,
294
            style: TextStyle(
×
295
              color: Colors.white,
296
              fontSize: 16,
297
              fontWeight: FontWeight.bold,
298
            ),
299
          ),
300
        ),
301
      ),
302
    );
303
  }
304

305
  Widget? buildActions(BuildContext context) {
×
306
    List<SpeedDialChild> actions = [
×
307
      SpeedDialChild(
×
308
        child: Icon(flash_status ? TablerIcons.bulb_off : TablerIcons.bulb),
×
309
        label: L10().toggleTorch,
×
310
        onTap: () async {
×
311
          controller.toggleTorch();
×
312
          if (mounted) {
×
313
            setState(() {
×
314
              flash_status = !flash_status;
×
315
            });
316
          }
317
        },
318
      ),
319
      SpeedDialChild(
×
320
        child: Icon(TablerIcons.camera),
×
321
        label: L10().switchCamera,
×
322
        onTap: () async {
×
323
          controller.switchCamera();
×
324
        },
325
      ),
326
    ];
327

328
    return SpeedDial(icon: Icons.more_horiz, children: actions);
×
329
  }
330

331
  @override
×
332
  Widget build(BuildContext context) {
333
    return Scaffold(
×
334
      appBar: AppBar(
×
335
        backgroundColor: COLOR_APP_BAR,
×
336
        title: Text(L10().scanBarcode),
×
337
      ),
338
      floatingActionButton: buildActions(context),
×
339
      body: GestureDetector(
×
340
        onTap: () async {
×
341
          if (mounted) {
×
342
            setState(() {
×
343
              // Toggle the 'scan paused' state
344
              scanning_paused = !scanning_paused;
×
345
            });
346
          }
347
        },
348
        child: Stack(
×
349
          children: <Widget>[
×
350
            Column(children: [Expanded(child: BarcodeReader(context))]),
×
351
            topCenterOverlay(),
×
352
            bottomCenterOverlay(),
×
353
          ],
354
        ),
355
      ),
356
    );
357
  }
358
}
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