Sander Roest

wip

@@ -2,7 +2,6 @@ import 'package:flutter/foundation.dart'; @@ -2,7 +2,6 @@ import 'package:flutter/foundation.dart';
2 import 'package:flutter/material.dart'; 2 import 'package:flutter/material.dart';
3 import 'package:mobile_scanner/mobile_scanner.dart'; 3 import 'package:mobile_scanner/mobile_scanner.dart';
4 import 'package:mobile_scanner_example/scanned_barcode_label.dart'; 4 import 'package:mobile_scanner_example/scanned_barcode_label.dart';
5 -  
6 import 'package:mobile_scanner_example/scanner_error_widget.dart'; 5 import 'package:mobile_scanner_example/scanner_error_widget.dart';
7 6
8 class BarcodeScannerWithScanWindow extends StatefulWidget { 7 class BarcodeScannerWithScanWindow extends StatefulWidget {
1 import 'package:flutter/material.dart'; 1 import 'package:flutter/material.dart';
  2 +import 'package:flutter/services.dart';
2 import 'package:mobile_scanner_example/barcode_scanner_analyze_image.dart'; 3 import 'package:mobile_scanner_example/barcode_scanner_analyze_image.dart';
3 import 'package:mobile_scanner_example/barcode_scanner_controller.dart'; 4 import 'package:mobile_scanner_example/barcode_scanner_controller.dart';
4 import 'package:mobile_scanner_example/barcode_scanner_listview.dart'; 5 import 'package:mobile_scanner_example/barcode_scanner_listview.dart';
@@ -8,8 +9,11 @@ import 'package:mobile_scanner_example/barcode_scanner_simple.dart'; @@ -8,8 +9,11 @@ import 'package:mobile_scanner_example/barcode_scanner_simple.dart';
8 import 'package:mobile_scanner_example/barcode_scanner_window.dart'; 9 import 'package:mobile_scanner_example/barcode_scanner_window.dart';
9 import 'package:mobile_scanner_example/barcode_scanner_zoom.dart'; 10 import 'package:mobile_scanner_example/barcode_scanner_zoom.dart';
10 import 'package:mobile_scanner_example/mobile_scanner_overlay.dart'; 11 import 'package:mobile_scanner_example/mobile_scanner_overlay.dart';
  12 +import 'package:mobile_scanner_example/picklist/picklist_result.dart';
11 13
12 -void main() { 14 +void main() async {
  15 + WidgetsFlutterBinding.ensureInitialized();
  16 + await SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]);
13 runApp( 17 runApp(
14 const MaterialApp( 18 const MaterialApp(
15 title: 'Mobile Scanner Example', 19 title: 'Mobile Scanner Example',
@@ -91,6 +95,11 @@ class MyHome extends StatelessWidget { @@ -91,6 +95,11 @@ class MyHome extends StatelessWidget {
91 'Analyze image from file', 95 'Analyze image from file',
92 const BarcodeScannerAnalyzeImage(), 96 const BarcodeScannerAnalyzeImage(),
93 ), 97 ),
  98 + _buildItem(
  99 + context,
  100 + 'Picklist mode',
  101 + const PicklistResult(),
  102 + ),
94 ], 103 ],
95 ), 104 ),
96 ), 105 ),
  1 +import 'dart:async';
  2 +
  3 +import 'package:flutter/material.dart';
  4 +import 'package:flutter/services.dart';
  5 +import 'package:mobile_scanner/mobile_scanner.dart';
  6 +import 'package:mobile_scanner_example/picklist/classes/detect_collision.dart';
  7 +import 'package:mobile_scanner_example/picklist/widgets/crosshair.dart';
  8 +import 'package:mobile_scanner_example/picklist/widgets/draw_detected_barcodes.dart';
  9 +import 'package:mobile_scanner_example/scanner_error_widget.dart';
  10 +
  11 +class BarcodeScannerPicklist extends StatefulWidget {
  12 + const BarcodeScannerPicklist({super.key});
  13 +
  14 + @override
  15 + State<BarcodeScannerPicklist> createState() => _BarcodeScannerPicklistState();
  16 +}
  17 +
  18 +class _BarcodeScannerPicklistState extends State<BarcodeScannerPicklist>
  19 + with WidgetsBindingObserver {
  20 + final _mobileScannerController = MobileScannerController(autoStart: false);
  21 + StreamSubscription<Object?>? _barcodesSubscription;
  22 +
  23 + final _scannerDisabled = ValueNotifier(false);
  24 +
  25 + late final Offset _crosshair;
  26 +
  27 + bool barcodeDetected = false;
  28 +
  29 + @override
  30 + void didChangeDependencies() {
  31 + _crosshair = MediaQuery.sizeOf(context).center(Offset.zero);
  32 + super.didChangeDependencies();
  33 + }
  34 +
  35 + @override
  36 + void initState() {
  37 + SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]);
  38 + WidgetsBinding.instance.addObserver(this);
  39 + _barcodesSubscription = _mobileScannerController.barcodes.listen(
  40 + _handleBarcodes,
  41 + );
  42 + super.initState();
  43 + unawaited(_mobileScannerController.start());
  44 + }
  45 +
  46 + @override
  47 + void didChangeAppLifecycleState(AppLifecycleState state) {
  48 + if (!_mobileScannerController.value.isInitialized) {
  49 + return;
  50 + }
  51 +
  52 + switch (state) {
  53 + case AppLifecycleState.detached:
  54 + case AppLifecycleState.hidden:
  55 + case AppLifecycleState.paused:
  56 + return;
  57 + case AppLifecycleState.resumed:
  58 + _barcodesSubscription =
  59 + _mobileScannerController.barcodes.listen(_handleBarcodes);
  60 +
  61 + unawaited(_mobileScannerController.start());
  62 + case AppLifecycleState.inactive:
  63 + unawaited(_barcodesSubscription?.cancel());
  64 + _barcodesSubscription = null;
  65 + unawaited(_mobileScannerController.stop());
  66 + }
  67 + }
  68 +
  69 + @override
  70 + void dispose() {
  71 + WidgetsBinding.instance.removeObserver(this);
  72 + unawaited(_barcodesSubscription?.cancel());
  73 + _barcodesSubscription = null;
  74 + super.dispose();
  75 + _mobileScannerController.dispose();
  76 + }
  77 +
  78 + void _handleBarcodes(BarcodeCapture barcodes) {
  79 + if (_scannerDisabled.value) {
  80 + return;
  81 + }
  82 +
  83 + for (final barcode in barcodes.barcodes) {
  84 + if (isOffsetInsideShape(
  85 + _crosshair,
  86 + barcode.corners,
  87 + )) {
  88 + if (!barcodeDetected) {
  89 + barcodeDetected = true;
  90 + Navigator.of(context).pop(barcode);
  91 + }
  92 + return;
  93 + }
  94 + }
  95 + }
  96 +
  97 + @override
  98 + Widget build(BuildContext context) {
  99 + const boxFit = BoxFit.contain;
  100 + return PopScope(
  101 + onPopInvokedWithResult: (didPop, result) {
  102 + if (didPop) {
  103 + SystemChrome.setPreferredOrientations([...DeviceOrientation.values]);
  104 + }
  105 + },
  106 + child: Scaffold(
  107 + appBar: AppBar(title: const Text('Picklist scanner')),
  108 + backgroundColor: Colors.black,
  109 + body: StreamBuilder(
  110 + stream: _mobileScannerController.barcodes,
  111 + builder: (context, snapshot) {
  112 + final barcodes = snapshot.data;
  113 + if (barcodes == null) {
  114 + debugPrint('ISNULL');
  115 + }
  116 + return Listener(
  117 + behavior: HitTestBehavior.opaque,
  118 + onPointerDown: (_) => _scannerDisabled.value = true,
  119 + onPointerUp: (_) => _scannerDisabled.value = false,
  120 + onPointerCancel: (_) => _scannerDisabled.value = false,
  121 + child: Stack(
  122 + fit: StackFit.expand,
  123 + children: [
  124 + MobileScanner(
  125 + controller: _mobileScannerController,
  126 + errorBuilder: (context, error, child) {
  127 + return ScannerErrorWidget(error: error);
  128 + },
  129 + fit: boxFit,
  130 + ),
  131 + ...drawDetectedBarcodes(
  132 + barcodes: barcodes?.barcodes,
  133 + cameraPreviewSize: _mobileScannerController.value.size,
  134 + fit: boxFit,
  135 + ),
  136 + ValueListenableBuilder(
  137 + valueListenable: _scannerDisabled,
  138 + builder: (context, value, child) {
  139 + return Crosshair(
  140 + scannerDisabled: value,
  141 + );
  142 + },
  143 + ),
  144 + ],
  145 + ),
  146 + );
  147 + },
  148 + )
  149 + // body: Stack(
  150 + // fit: StackFit.expand,
  151 + // children: [
  152 + // MobileScanner(
  153 + // controller: _mobileScannerController,
  154 + // errorBuilder: (context, error, child) {
  155 + // return ScannerErrorWidget(error: error);
  156 + // },
  157 + // fit: boxFit,
  158 + // ),
  159 + // ...drawDetectedBarcodes(
  160 + // controller: _mobileScannerController,
  161 + // cameraPreviewSize: _mobileScannerController.value.size,
  162 + // fit: boxFit,
  163 + // ),
  164 + // ...drawDetectedBarcodes(
  165 + // controller: _mobileScannerController,
  166 + // cameraPreviewSize: _mobileScannerController.value.size,
  167 + // fit: boxFit,
  168 + // ),
  169 + // // barcodes: _mobileScannerController. value. _barcodes.value,
  170 + // // cameraPreviewSize: _mobileScannerController.value.size,
  171 + // // fit: boxFit,
  172 + // // ),
  173 + // CustomPaint(
  174 + // painter: BarcodeOverlay(
  175 + // barcodeCorners: [
  176 + // const Offset(0, 0),
  177 + // const Offset(50, 0),
  178 + // const Offset(50, 50),
  179 + // const Offset(0, 50)
  180 + // ],
  181 + // barcodeSize: Size(50, 50),
  182 + // boxFit: boxFit,
  183 + // cameraPreviewSize: _mobileScannerController.value.size,
  184 + // ),
  185 + // ),
  186 + // Crosshair(
  187 + // crosshairRectangle: _crosshairRectangle,
  188 + // scannerDisabled: _scannerDisabled.value,
  189 + // ),
  190 + // ],
  191 + // ),
  192 + ,
  193 + ),
  194 + );
  195 + }
  196 +}
  1 +//Some magic created by chatGPT
  2 +
  3 +import 'package:flutter/material.dart';
  4 +
  5 +bool isOffsetInsideShape(Offset point, List<Offset> shape) {
  6 + return _isPointInPolygon(shape, point);
  7 +}
  8 +
  9 +bool _isPointInPolygon(List<Offset> polygon, Offset point) {
  10 + // Use the ray-casting algorithm for checking if a point is inside a polygon
  11 + bool inside = false;
  12 + for (int i = 0, j = polygon.length - 1; i < polygon.length; j = i++) {
  13 + if ((polygon[i].dy > point.dy) != (polygon[j].dy > point.dy) &&
  14 + (point.dx <
  15 + (polygon[j].dx - polygon[i].dx) *
  16 + (point.dy - polygon[i].dy) /
  17 + (polygon[j].dy - polygon[i].dy) +
  18 + polygon[i].dx)) {
  19 + inside = !inside;
  20 + }
  21 + }
  22 + return inside;
  23 +}
  24 +
  25 +// import 'package:flutter/material.dart';
  26 +//
  27 +// bool crosshairFullyFitsIntoShape(Rect rect, List<Offset> shape) {
  28 +// final List<Offset> rectCorners = [
  29 +// Offset(rect.left, rect.top),
  30 +// Offset(rect.right, rect.top),
  31 +// Offset(rect.right, rect.bottom),
  32 +// Offset(rect.left, rect.bottom),
  33 +// ];
  34 +//
  35 +// // Check if all rect corners are inside the shape
  36 +// for (final Offset corner in rectCorners) {
  37 +// if (!_isPointInPolygon(shape, corner)) {
  38 +// return false; // If any corner is outside, the rectangle doesn't fit fully
  39 +// }
  40 +// }
  41 +//
  42 +// return true; // All corners are inside the shape
  43 +// }
  44 +//
  45 +// bool _isPointInPolygon(List<Offset> polygon, Offset point) {
  46 +// // Use the ray-casting algorithm for checking if a point is inside a polygon
  47 +// bool inside = false;
  48 +// for (int i = 0, j = polygon.length - 1; i < polygon.length; j = i++) {
  49 +// if ((polygon[i].dy > point.dy) != (polygon[j].dy > point.dy) &&
  50 +// (point.dx <
  51 +// (polygon[j].dx - polygon[i].dx) *
  52 +// (point.dy - polygon[i].dy) /
  53 +// (polygon[j].dy - polygon[i].dy) +
  54 +// polygon[i].dx)) {
  55 +// inside = !inside;
  56 +// }
  57 +// }
  58 +// return inside;
  59 +// }
  60 +// // import 'package:flutter/material.dart';
  61 +// //
  62 +// // bool crosshairTouchesBarcode(Rect rect, List<Offset> shape) {
  63 +// // final List<Offset> rectCorners = [
  64 +// // Offset(rect.left, rect.top),
  65 +// // Offset(rect.right, rect.top),
  66 +// // Offset(rect.right, rect.bottom),
  67 +// // Offset(rect.left, rect.bottom),
  68 +// // ];
  69 +// // final List<Offset> edges = [shape[0], shape[1], shape[2], shape[3], shape[0]];
  70 +// //
  71 +// // // Check edge intersection
  72 +// // for (int i = 0; i < edges.length - 1; i++) {
  73 +// // for (int j = 0; j < rectCorners.length; j++) {
  74 +// // final int next = (j + 1) % rectCorners.length;
  75 +// // if (_checkIntersection(
  76 +// // edges[i],
  77 +// // edges[i + 1],
  78 +// // rectCorners[j],
  79 +// // rectCorners[next],
  80 +// // )) {
  81 +// // return true;
  82 +// // }
  83 +// // }
  84 +// // }
  85 +// //
  86 +// // // Check if any rect corner is inside the shape
  87 +// // for (final Offset corner in rectCorners) {
  88 +// // if (_isPointInPolygon(shape, corner)) {
  89 +// // return true;
  90 +// // }
  91 +// // }
  92 +// //
  93 +// // return false;
  94 +// // }
  95 +// //
  96 +// // bool _checkIntersection(Offset p1, Offset p2, Offset p3, Offset p4) {
  97 +// // // Calculate the intersection of two line segments
  98 +// // double s1X;
  99 +// // double s1Y;
  100 +// // double s2X;
  101 +// // double s2Y;
  102 +// // s1X = p2.dx - p1.dx;
  103 +// // s1Y = p2.dy - p1.dy;
  104 +// // s2X = p4.dx - p3.dx;
  105 +// // s2Y = p4.dy - p3.dy;
  106 +// //
  107 +// // double s;
  108 +// // double t;
  109 +// // s = (-s1Y * (p1.dx - p3.dx) + s1X * (p1.dy - p3.dy)) /
  110 +// // (-s2X * s1Y + s1X * s2Y);
  111 +// // t = (s2X * (p1.dy - p3.dy) - s2Y * (p1.dx - p3.dx)) /
  112 +// // (-s2X * s1Y + s1X * s2Y);
  113 +// //
  114 +// // return s >= 0 && s <= 1 && t >= 0 && t <= 1;
  115 +// // }
  116 +// //
  117 +// // bool _isPointInPolygon(List<Offset> polygon, Offset point) {
  118 +// // // Ray-casting algorithm for checking if a point is inside a polygon
  119 +// // bool inside = false;
  120 +// // for (int i = 0, j = polygon.length - 1; i < polygon.length; j = i++) {
  121 +// // if ((polygon[i].dy > point.dy) != (polygon[j].dy > point.dy) &&
  122 +// // (point.dx <
  123 +// // (polygon[j].dx - polygon[i].dx) *
  124 +// // (point.dy - polygon[i].dy) /
  125 +// // (polygon[j].dy - polygon[i].dy) +
  126 +// // polygon[i].dx)) {
  127 +// // inside = !inside;
  128 +// // }
  129 +// // }
  130 +// // return inside;
  131 +// // }
  1 +import 'package:flutter/material.dart';
  2 +import 'package:mobile_scanner/mobile_scanner.dart';
  3 +import 'package:mobile_scanner_example/picklist/barcode_scanner_picklist.dart';
  4 +
  5 +class PicklistResult extends StatefulWidget {
  6 + const PicklistResult({super.key});
  7 +
  8 + @override
  9 + State<PicklistResult> createState() => _PicklistResultState();
  10 +}
  11 +
  12 +class _PicklistResultState extends State<PicklistResult> {
  13 + String barcode = 'Scan Something!';
  14 +
  15 + @override
  16 + Widget build(BuildContext context) {
  17 + return Scaffold(
  18 + appBar: AppBar(title: const Text('Picklist mode')),
  19 + body: SafeArea(
  20 + child: Padding(
  21 + padding: const EdgeInsets.symmetric(horizontal: 16.0),
  22 + child: Center(
  23 + child: Column(
  24 + mainAxisAlignment: MainAxisAlignment.center,
  25 + children: [
  26 + Text(barcode),
  27 + ElevatedButton(
  28 + onPressed: () async {
  29 + final scannedBarcode =
  30 + await Navigator.of(context).push<Barcode>(
  31 + MaterialPageRoute(
  32 + builder: (context) => const BarcodeScannerPicklist(),
  33 + ),
  34 + );
  35 + setState(
  36 + () {
  37 + if (scannedBarcode == null) {
  38 + barcode = 'Scan Something!';
  39 + return;
  40 + }
  41 + if (scannedBarcode.displayValue == null) {
  42 + barcode = '>>binary<<';
  43 + return;
  44 + }
  45 + barcode = scannedBarcode.displayValue!;
  46 + },
  47 + );
  48 + },
  49 + child: const Text('Scan'),
  50 + ),
  51 + ],
  52 + ),
  53 + ),
  54 + ),
  55 + ),
  56 + );
  57 + }
  58 +}
  1 +import 'package:flutter/foundation.dart';
  2 +import 'package:flutter/material.dart';
  3 +
  4 +class BarcodeOverlay extends CustomPainter {
  5 + BarcodeOverlay({
  6 + required this.barcodeCorners,
  7 + required this.barcodeSize,
  8 + required this.boxFit,
  9 + required this.cameraPreviewSize,
  10 + });
  11 +
  12 + final List<Offset> barcodeCorners;
  13 + final Size barcodeSize;
  14 + final BoxFit boxFit;
  15 + final Size cameraPreviewSize;
  16 +
  17 + @override
  18 + void paint(Canvas canvas, Size size) {
  19 + if (barcodeCorners.isEmpty ||
  20 + barcodeSize.isEmpty ||
  21 + cameraPreviewSize.isEmpty) {
  22 + return;
  23 + }
  24 +
  25 + final adjustedSize = applyBoxFit(boxFit, cameraPreviewSize, size);
  26 +
  27 + double verticalPadding = size.height - adjustedSize.destination.height;
  28 + double horizontalPadding = size.width - adjustedSize.destination.width;
  29 + if (verticalPadding > 0) {
  30 + verticalPadding = verticalPadding / 2;
  31 + } else {
  32 + verticalPadding = 0;
  33 + }
  34 +
  35 + if (horizontalPadding > 0) {
  36 + horizontalPadding = horizontalPadding / 2;
  37 + } else {
  38 + horizontalPadding = 0;
  39 + }
  40 +
  41 + final double ratioWidth;
  42 + final double ratioHeight;
  43 +
  44 + if (!kIsWeb && defaultTargetPlatform == TargetPlatform.iOS) {
  45 + ratioWidth = barcodeSize.width / adjustedSize.destination.width;
  46 + ratioHeight = barcodeSize.height / adjustedSize.destination.height;
  47 + } else {
  48 + ratioWidth = cameraPreviewSize.width / adjustedSize.destination.width;
  49 + ratioHeight = cameraPreviewSize.height / adjustedSize.destination.height;
  50 + }
  51 +
  52 + final List<Offset> adjustedOffset = [
  53 + for (final offset in barcodeCorners)
  54 + Offset(
  55 + offset.dx / ratioWidth + horizontalPadding,
  56 + offset.dy / ratioHeight + verticalPadding,
  57 + ),
  58 + ];
  59 +
  60 + final cutoutPath = Path()..addPolygon(adjustedOffset, true);
  61 +
  62 + final backgroundPaint = Paint()
  63 + ..color = Colors.red.withOpacity(1.0)
  64 + ..style = PaintingStyle.fill
  65 + ..blendMode = BlendMode.dstOut;
  66 +
  67 + canvas.drawPath(cutoutPath, backgroundPaint);
  68 + }
  69 +
  70 + @override
  71 + bool shouldRepaint(covariant CustomPainter oldDelegate) {
  72 + return false;
  73 + }
  74 +}
  1 +import 'package:flutter/material.dart';
  2 +
  3 +class Crosshair extends StatelessWidget {
  4 + const Crosshair({
  5 + super.key,
  6 + required this.scannerDisabled,
  7 + });
  8 +
  9 + final bool scannerDisabled;
  10 +
  11 + @override
  12 + Widget build(BuildContext context) {
  13 + return Center(
  14 + child: Icon(
  15 + Icons.close,
  16 + color: scannerDisabled ? Colors.green : Colors.red,
  17 + ),
  18 + );
  19 + }
  20 +}
  1 +import 'package:flutter/material.dart';
  2 +import 'package:mobile_scanner/mobile_scanner.dart';
  3 +import 'package:mobile_scanner_example/picklist/widgets/barcode_overlay.dart';
  4 +
  5 +List<Widget> drawDetectedBarcodes({
  6 + required List<Barcode>? barcodes,
  7 + required Size cameraPreviewSize,
  8 + required BoxFit fit,
  9 +}) {
  10 + final barcodeWidgets = <Widget>[];
  11 + if (barcodes == null || barcodes.isEmpty) {
  12 + debugPrint('EMPTY!!!');
  13 + }
  14 + if (barcodes != null) {
  15 + for (final barcode in barcodes) {
  16 + barcodeWidgets.add(
  17 + CustomPaint(
  18 + painter: BarcodeOverlay(
  19 + barcodeCorners: barcode.corners,
  20 + barcodeSize: barcode.size,
  21 + boxFit: fit,
  22 + cameraPreviewSize: cameraPreviewSize,
  23 + ),
  24 + ),
  25 + );
  26 + debugPrint(
  27 + 'barcodeCorners => ${barcode.corners.map((e) => 'x: ${e.dx}, y: ${e.dy} ')}, barcodeSize => width: ${barcode.size.width}, height: ${barcode.size.height}, cameraPreviewSize => width: ${cameraPreviewSize.width}, height: ${cameraPreviewSize.height} ',
  28 + );
  29 + }
  30 + debugPrint(barcodeWidgets.length.toString());
  31 + }
  32 + return barcodeWidgets;
  33 +}