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

inventree / inventree-app / 15977486612

30 Jun 2025 03:44PM UTC coverage: 1.544% (+0.001%) from 1.543%
15977486612

Pull #670

github

web-flow
Merge 3d7ad6a08 into 5bd5d34b2
Pull Request #670: Use raw value of UTF-8 encoded barcodes

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

768 of 49752 relevant lines covered (1.54%)

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();
×
58
    WakelockPlus.disable();
×
59
  }
60

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

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

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

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

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

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

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

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

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

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

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

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

139
    pauseScan();
×
140

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

147
    resumeScan();
×
148

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

313
  @override
×
314
  Widget build(BuildContext context) {
315
    return Scaffold(
×
316
      appBar: AppBar(
×
317
        backgroundColor: COLOR_APP_BAR,
318
        title: Text(L10().scanBarcode),
×
319
      ),
320
      floatingActionButton: buildActions(context),
×
321
      body: GestureDetector(
×
322
        onTap: () async {
×
323
          if (mounted) {
×
324
            setState(() {
×
325
              // Toggle the 'scan paused' state
326
              scanning_paused = !scanning_paused;
×
327
            });
328
          }
329
        },
330
        child: Stack(
×
331
          children: <Widget>[
×
332
            Column(children: [Expanded(child: BarcodeReader(context))]),
×
333
            topCenterOverlay(),
×
334
            bottomCenterOverlay(),
×
335
          ],
336
        ),
337
      ),
338
    );
339
  }
340
}
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