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

inventree / inventree-app / 15820811198

23 Jun 2025 09:42AM UTC coverage: 1.566%. Remained the same
15820811198

push

github

web-flow
Use FVM in GitHub Actions and migrate to Flutter 3.32.4 (#641)

* feat: implement Flutter version management using FVM across CI workflows

* Flutter 3.29.3 + minor Package upgrades

* Replace deprecated `withOpacity`

* Upgrade major package versions without breaking changes.

* Disable unnecessary_async rule

Re-enable later

* New language version and automated fixes

- unnecessary_breaks
- unnecessary_underscore

* Update BUILDING.md to use fvm commands

* Add gitignore files for Android and iOS build artifacts

* Migrate iOS dependencies to Swift Package Manager

This is being done automatically by Flutter

* Flutter 3.32.4

* New sdk version

* docs: add IDE setup instructions and troubleshooting guide for FVM integration

0 of 14 new or added lines in 3 files covered. (0.0%)

20 existing lines in 6 files now uncovered.

759 of 48469 relevant lines covered (1.57%)

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})
×
NEW
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 {
×
NEW
66
    bool _single = await InvenTreeSettingsManager().getBool(
×
67
      INV_BARCODE_SCAN_SINGLE,
68
      false,
69
    );
70

71
    int _delay =
NEW
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 {
UNCOV
86
    if (mounted) {
×
87
      setState(() {
×
88
        scanning_paused = true;
×
89
      });
90
    }
91
  }
92

93
  @override
×
94
  Future<void> resumeScan() async {
UNCOV
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
      });
NEW
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) {
×
UNCOV
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(
×
NEW
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) {
×
UNCOV
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(
×
NEW
261
          padding: EdgeInsets.only(left: 10, right: 10, top: 75, bottom: 10),
×
UNCOV
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() {
×
276
    String info_text =
NEW
277
        scanning_paused ? L10().barcodeScanPaused : L10().barcodeScanPause;
×
278

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

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

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

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

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

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