diff --git a/README.md b/README.md index ea9af2f..ce192a5 100644 --- a/README.md +++ b/README.md @@ -118,8 +118,8 @@ Widget build(BuildContext context) { final result = zx.encodeBarcode( 'Text to encode', format: Format.QRCode, - width: 300, - height: 300, + width: 120, + height: 120, margin: 10, eccLevel: 0, ); diff --git a/example/lib/main.dart b/example/lib/main.dart index c615922..122460f 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -1,6 +1,7 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_zxing/flutter_zxing.dart'; +import 'package:image_picker/image_picker.dart'; void main() { zx.setLogEnabled(kDebugMode); @@ -34,6 +35,12 @@ class DemoPage extends StatefulWidget { class _DemoPageState extends State { Uint8List? createdCodeBytes; + Code? result; + + bool showDebugInfo = true; + int successScans = 0; + int failedScans = 0; + @override Widget build(BuildContext context) { return DefaultTabController( @@ -52,26 +59,34 @@ class _DemoPageState extends State { physics: const NeverScrollableScrollPhysics(), children: [ if (kIsWeb) - Center( - child: Text( - 'Web is not supported yet.', - style: Theme.of(context).textTheme.headline6, - ), + const UnsupportedPlatformWidget() + else if (result != null) + ScanResultWidget( + result: result?.text, + onScanAgain: () => setState(() => result = null), ) else - ReaderWidget( - onScan: (value) { - showMessage(context, 'Scanned: ${value.text ?? ''}'); - }, - tryInverted: true, + Stack( + children: [ + ReaderWidget( + onScan: _onScanSuccess, + onScanFailure: () => _onScanFailure(null), + tryInverted: true, + ), + ScanFromGalleryWidget( + onScan: _onScanSuccess, + onScanFailure: _onScanFailure, + ), + if (showDebugInfo) + DebugInfoWidget( + successScans: successScans, + failedScans: failedScans, + onReset: _onReset, + ), + ], ), if (kIsWeb) - Center( - child: Text( - 'Web is not supported yet.', - style: Theme.of(context).textTheme.headline6, - ), - ) + const UnsupportedPlatformWidget() else ListView( children: [ @@ -85,7 +100,7 @@ class _DemoPageState extends State { }); }, onError: (error) { - showMessage(context, 'Error: $error'); + _showMessage(context, 'Error: $error'); }, ), if (createdCodeBytes != null) @@ -98,12 +113,162 @@ class _DemoPageState extends State { ); } - showMessage(BuildContext context, String message) { - debugPrint(message); + _onScanSuccess(value) { + setState(() { + successScans++; + result = value; + }); + } + + _onScanFailure(String? error) { + setState(() { + failedScans++; + }); + if (error != null) { + _showMessage(context, error); + } + } + + _showMessage(BuildContext context, String message) { ScaffoldMessenger.of(context).hideCurrentSnackBar(); ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text(message), + SnackBar(content: Text(message)), + ); + } + + _onReset() { + setState(() { + successScans = 0; + failedScans = 0; + }); + } +} + +class ScanResultWidget extends StatelessWidget { + const ScanResultWidget({ + Key? key, + this.result, + this.onScanAgain, + }) : super(key: key); + + final String? result; + final Function()? onScanAgain; + + @override + Widget build(BuildContext context) { + return Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + result ?? '', + style: Theme.of(context).textTheme.headline6, + ), + const SizedBox(height: 20), + ElevatedButton( + onPressed: onScanAgain, + child: const Text('Scan Again'), + ), + ], + ), + ); + } +} + +class ScanFromGalleryWidget extends StatelessWidget { + const ScanFromGalleryWidget({ + Key? key, + this.onScan, + this.onScanFailure, + }) : super(key: key); + + final Function(Code?)? onScan; + final Function(String)? onScanFailure; + + @override + Widget build(BuildContext context) { + return Positioned( + bottom: 20, + right: 20, + child: FloatingActionButton( + onPressed: _onFromGalleryButtonTapped, + child: const Icon(Icons.image), + ), + ); + } + + void _onFromGalleryButtonTapped() async { + final XFile? file = + await ImagePicker().pickImage(source: ImageSource.gallery); + if (file != null) { + final Code? result = await zx.readBarcodeImagePath( + file, + params: Params(tryInverted: true), + ); + if (result != null && result.isValid) { + onScan?.call(result); + } else { + onScanFailure?.call('Failed to read barcode from image'); + } + } + } +} + +class DebugInfoWidget extends StatelessWidget { + const DebugInfoWidget({ + Key? key, + required this.successScans, + required this.failedScans, + this.onReset, + }) : super(key: key); + + final int successScans; + final int failedScans; + + final Function()? onReset; + + @override + Widget build(BuildContext context) { + return Align( + alignment: Alignment.topCenter, + child: Padding( + padding: const EdgeInsets.all(8.0), + child: ClipRRect( + borderRadius: BorderRadius.circular(10), + child: Container( + color: Colors.white.withOpacity(0.7), + padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 5), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + 'Success: $successScans\nFailed: $failedScans', + style: Theme.of(context).textTheme.headline6, + ), + TextButton( + onPressed: onReset, + child: const Text('Reset'), + ), + ], + ), + ), + ), + ), + ); + } +} + +class UnsupportedPlatformWidget extends StatelessWidget { + const UnsupportedPlatformWidget({ + Key? key, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return Center( + child: Text( + 'This platform is not supported yet.', + style: Theme.of(context).textTheme.headline6, ), ); } diff --git a/example/pubspec.lock b/example/pubspec.lock index c901941..5e42cf6 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -148,7 +148,7 @@ packages: path: ".." relative: true source: path - version: "0.9.0" + version: "0.9.1" font_awesome_flutter: dependency: "direct main" description: @@ -204,7 +204,7 @@ packages: name: image_picker_ios url: "https://pub.dartlang.org" source: hosted - version: "0.8.6+4" + version: "0.8.6+5" image_picker_platform_interface: dependency: transitive description: diff --git a/lib/src/logic/barcode_reader.dart b/lib/src/logic/barcode_reader.dart index a1911e3..506b959 100644 --- a/lib/src/logic/barcode_reader.dart +++ b/lib/src/logic/barcode_reader.dart @@ -53,7 +53,22 @@ Code zxingReadBarcode( required int width, required int height, Params? params, -}) => +}) { + Code result = _readBarcode(bytes, width, height, params); + if (!result.isValid && params != null && params.tryInverted == true) { + // try to invert the image and read again + final Uint8List invertedBytes = invertImage(bytes); + result = _readBarcode(invertedBytes, width, height, params); + } + return result; +} + +Code _readBarcode( + Uint8List bytes, + int width, + int height, + Params? params, +) => bindings .readBarcode( bytes.allocatePointer(), diff --git a/lib/src/logic/barcodes_reader.dart b/lib/src/logic/barcodes_reader.dart index 41960aa..cf4be03 100644 --- a/lib/src/logic/barcodes_reader.dart +++ b/lib/src/logic/barcodes_reader.dart @@ -54,6 +54,21 @@ List zxingReadBarcodes( required int height, Params? params, }) { + List results = _readBarcodes(bytes, width, height, params); + if (results.isEmpty && params != null && params.tryInverted == true) { + // try to invert the image and read again + final Uint8List invertedBytes = invertImage(bytes); + results = _readBarcodes(invertedBytes, width, height, params); + } + return results; +} + +List _readBarcodes( + Uint8List bytes, + int width, + int height, + Params? params, +) { final CodeResults result = bindings.readBarcodes( bytes.allocatePointer(), params?.format ?? Format.any, diff --git a/lib/src/logic/zxing.dart b/lib/src/logic/zxing.dart index d2852a3..e4c22e4 100644 --- a/lib/src/logic/zxing.dart +++ b/lib/src/logic/zxing.dart @@ -12,6 +12,7 @@ import 'package:image/image.dart' as imglib; import '../../generated_bindings.dart'; import '../models/models.dart'; import '../utils/extentions.dart'; +import '../utils/image_converter.dart'; import '../utils/isolate_utils.dart'; part 'barcode_encoder.dart'; diff --git a/lib/src/ui/reader_widget.dart b/lib/src/ui/reader_widget.dart index d0e8561..c8973c4 100644 --- a/lib/src/ui/reader_widget.dart +++ b/lib/src/ui/reader_widget.dart @@ -13,6 +13,7 @@ class ReaderWidget extends StatefulWidget { const ReaderWidget({ super.key, required this.onScan, + this.onScanFailure, this.onControllerCreated, this.codeFormat = Format.any, this.tryHarder = false, @@ -30,6 +31,7 @@ class ReaderWidget extends StatefulWidget { }); final Function(Code) onScan; + final Function()? onScanFailure; final Function(CameraController?)? onControllerCreated; final int codeFormat; final bool tryHarder; @@ -183,6 +185,8 @@ class _ReaderWidgetState extends State widget.onScan(result); setState(() {}); await Future.delayed(widget.scanDelaySuccess); + } else { + widget.onScanFailure?.call(); } } on FileSystemException catch (e) { debugPrint(e.message); diff --git a/lib/src/utils/isolate_utils.dart b/lib/src/utils/isolate_utils.dart index 2a89605..f7ced1a 100644 --- a/lib/src/utils/isolate_utils.dart +++ b/lib/src/utils/isolate_utils.dart @@ -58,24 +58,13 @@ class IsolateUtils { final CameraImage image = isolateData.cameraImage; final Uint8List bytes = await convertImage(image); - Code result = zxingReadBarcode( + final Code result = zxingReadBarcode( bytes, width: image.width, height: image.height, params: isolateData.params, ); - if (!result.isValid && isolateData.params.tryInverted) { - // try to invert the image and read again - final Uint8List invertedBytes = invertImage(bytes); - result = zxingReadBarcode( - invertedBytes, - width: image.width, - height: image.height, - params: isolateData.params, - ); - } - isolateData.responsePort?.send(result); } catch (e) { isolateData.responsePort?.send(e);