import 'dart:ui'; 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(false); runApp(const MyApp()); } class MyApp extends StatefulWidget { const MyApp({Key? key}) : super(key: key); @override State createState() => _MyAppState(); } class _MyAppState extends State { @override Widget build(BuildContext context) { return const MaterialApp( title: 'Flutter Zxing Example', home: DemoPage(), ); } } class DemoPage extends StatefulWidget { const DemoPage({Key? key}) : super(key: key); @override State createState() => _DemoPageState(); } class _DemoPageState extends State { Uint8List? createdCodeBytes; Code? result; List multiResult = []; bool showDebugInfo = true; int successScans = 0; int failedScans = 0; @override Widget build(BuildContext context) { final isCameraSupported = defaultTargetPlatform == TargetPlatform.iOS || defaultTargetPlatform == TargetPlatform.android; return DefaultTabController( length: 2, child: Scaffold( appBar: AppBar( title: const Text('Flutter Zxing Example'), bottom: const TabBar( tabs: [ Tab(text: 'Scan Code'), Tab(text: 'Create Code'), ], ), ), body: TabBarView( physics: const NeverScrollableScrollPhysics(), children: [ if (kIsWeb) const UnsupportedPlatformWidget() else if (!isCameraSupported) const Center( child: Text('Camera not supported on this platform'), ) else if (result != null && result?.isValid == true) ScanResultWidget( result: result, onScanAgain: () => setState(() => result = null), ) else Stack( children: [ ReaderWidget( onScan: _onScanSuccess, onScanFailure: _onScanFailure, onMultiScan: _onMultiScanSuccess, onMultiScanFailure: _onMultiScanFailure, isMultiScan: true, // showScannerOverlay: false, // scanDelay: const Duration(milliseconds: 0), // tryInverted: true, ), // show multi results as rectangles // if (multiResult.isNotEmpty) // Positioned.fill( // child: CustomPaint( // painter: MultiScanPainter( // codes: multiResult, // size: Size( // MediaQuery.of(context).size.width, // MediaQuery.of(context).size.height, // ), // ), // ), // ), ScanFromGalleryWidget( onScan: _onScanSuccess, onScanFailure: _onScanFailure, ), if (showDebugInfo) DebugInfoWidget( successScans: successScans, failedScans: failedScans, error: result?.error, duration: result?.duration ?? 0, onReset: _onReset, ), ], ), if (kIsWeb) const UnsupportedPlatformWidget() else ListView( children: [ WriterWidget( messages: const Messages( createButton: 'Create Code', ), onSuccess: (result, bytes) { setState(() { createdCodeBytes = bytes; }); }, onError: (error) { _showMessage(context, 'Error: $error'); }, ), if (createdCodeBytes != null) Image.memory(createdCodeBytes ?? Uint8List(0), height: 200), ], ), ], ), ), ); } _onScanSuccess(Code? code) { setState(() { successScans++; result = code; }); } _onScanFailure(Code? code) { setState(() { failedScans++; result = code; }); if (code?.error?.isNotEmpty == true) { _showMessage(context, 'Error: ${code?.error}'); } } _onMultiScanSuccess(List codes) { setState(() { successScans++; multiResult = codes; }); } _onMultiScanFailure(List codes) { setState(() { failedScans++; multiResult = codes; }); if (result?.error?.isNotEmpty == true) { _showMessage(context, 'Error: ${codes.first.error}'); } } _showMessage(BuildContext context, String message) { ScaffoldMessenger.of(context).hideCurrentSnackBar(); ScaffoldMessenger.of(context).showSnackBar( 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 Code? result; final Function()? onScanAgain; @override Widget build(BuildContext context) { return Center( child: Padding( padding: const EdgeInsets.all(20.0), child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Text( result?.format?.name ?? '', style: Theme.of(context).textTheme.headline5, ), const SizedBox(height: 20), Text( result?.text ?? '', style: Theme.of(context).textTheme.headline6, ), const SizedBox(height: 20), Text( 'Inverted: ${result?.isInverted}\t\tMirrored: ${result?.isMirrored}', style: Theme.of(context).textTheme.bodyText2, ), const SizedBox(height: 40), 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(Code?)? 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: DecodeParams(tryInverted: true), ); if (result != null && result.isValid) { onScan?.call(result); } else { result?.error = 'No barcode found'; onScanFailure?.call(result); } } } } class DebugInfoWidget extends StatelessWidget { const DebugInfoWidget({ Key? key, required this.successScans, required this.failedScans, this.error, this.duration = 0, this.onReset, }) : super(key: key); final int successScans; final int failedScans; final String? error; final int duration; final Function()? onReset; @override Widget build(BuildContext context) { return Align( alignment: Alignment.topLeft, 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\nDuration: $duration ms', style: Theme.of(context).textTheme.bodySmall, ), 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, ), ); } } class MultiResultWidget extends StatelessWidget { const MultiResultWidget({ super.key, this.results = const [], }); final List results; @override Widget build(BuildContext context) { return Container(); } } class MultiScanPainter extends CustomPainter { MultiScanPainter({ required this.codes, required this.size, // required this.scale, // required this.offset, }); final List codes; final Size size; final double scale = 1; final Offset offset = Offset.zero; @override void paint(Canvas canvas, Size size) { final Paint paint = Paint() ..color = Colors.green ..strokeWidth = 2 ..style = PaintingStyle.stroke; for (final Code code in codes) { final position = code.position; if (position == null) { continue; } // position to points final List points = positionToPoints(position); // debugPrint('w: ${position.imageWidth} h: ${position.imageHeight}'); // print(points); canvas.drawPoints(PointMode.polygon, points, paint); } } @override bool shouldRepaint(MultiScanPainter oldDelegate) { return true; } List positionToPoints(Position pos) { return [ Offset(pos.topLeftX.toDouble(), pos.topLeftY.toDouble()), Offset(pos.topRightX.toDouble(), pos.topRightY.toDouble()), Offset(pos.bottomRightX.toDouble(), pos.bottomRightY.toDouble()), Offset(pos.bottomLeftX.toDouble(), pos.bottomLeftY.toDouble()), ]; } }