import 'package:flutter/material.dart'; import 'package:qrscanner_zxing/qrscanner_zxing_view.dart'; import '../../oath/models.dart'; /// Status of view state enum _ScanStatus { looking, error, success } class OverlayClipper extends CustomClipper { /// helper method to calculate position of the rect Rect _getOverlayRect(Size size, double width) => Rect.fromCenter( center: Offset(size.width / 2, size.height / 2), width: width, height: width); @override Path getClip(Size size) { const r = 40.0; var w = size.width - 40; return Path() ..addRect(Rect.fromLTWH(0, 0, size.width, size.height)) ..addRRect(RRect.fromRectXY(_getOverlayRect(size, w), r, r)) ..fillType = PathFillType.evenOdd; } @override bool shouldReclip(covariant CustomClipper oldClipper) => true; } class _MobileScannerWrapper extends StatelessWidget { final Function(String) onDetect; final _ScanStatus status; const _MobileScannerWrapper({ required this.onDetect, required this.status, }); @override Widget build(BuildContext context) { var backgroundColor = status == _ScanStatus.looking ? Colors.white : status == _ScanStatus.error ? Colors.red.shade900 : Colors.green.shade900; var size = MediaQuery.of(context).size; var positionRect = Rect.fromCenter( center: Offset(size.width / 2, size.height / 2 - 51), width: size.width - 38, height: size.width - 38); return Stack(children: [ QRScannerZxingView( marginPct: 20, onDetect: (barCode) { onDetect.call(barCode); }), ClipPath( clipper: OverlayClipper(), child: Opacity( opacity: 0.3, child: ColoredBox( color: backgroundColor, child: Column( mainAxisSize: MainAxisSize.max, mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.stretch, children: const [Spacer()], )))), if (status == _ScanStatus.success) Positioned.fromRect( rect: positionRect, child: Icon( Icons.check_circle, size: 200, color: Colors.green.shade400, )), if (status == _ScanStatus.error) Positioned.fromRect( rect: positionRect, child: Icon( Icons.error, size: 200, color: Colors.red.shade400, )), ]); } } class QrScannerView extends StatefulWidget { const QrScannerView({super.key}); @override State createState() => _QrScannerViewState(); } class _QrScannerViewState extends State { String? _scannedString; // will be used later // ignore: unused_field CredentialData? _credentialData; _ScanStatus _status = _ScanStatus.looking; void setError() { _credentialData = null; _scannedString = null; _status = _ScanStatus.error; Future.delayed(const Duration(milliseconds: 2000), () { resetError(); }); } void resetError() { setState(() { _credentialData = null; _scannedString = null; _status = _ScanStatus.looking; }); } void handleResult(String barCode) { if (_status != _ScanStatus.looking) { // on success and error ignore reported codes return; } setState(() { if (barCode.isNotEmpty) { try { var parsedCredential = CredentialData.fromUri(Uri.parse(barCode)); _credentialData = parsedCredential; _scannedString = barCode; _status = _ScanStatus.success; final navigator = Navigator.of(context); Future.delayed(const Duration(milliseconds: 800), () { if (navigator.canPop()) { // prevent several callbacks navigator.pop(_scannedString); } }); } on ArgumentError catch (_) { setError(); } catch (e) { setError(); } } else { setError(); } }); } @override Widget build(BuildContext context) { return SafeArea( child: Scaffold( appBar: AppBar( title: const Text('Scan QR code'), leading: BackButton( onPressed: () { Navigator.of(context).pop(); }, ), ), body: Stack(children: [ _MobileScannerWrapper( status: _status, onDetect: (scannedData) => handleResult(scannedData), ), Padding( padding: const EdgeInsets.symmetric(vertical: 32, horizontal: 32), child: Column( mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisSize: MainAxisSize.max, crossAxisAlignment: CrossAxisAlignment.stretch, children: [ Column(children: [ const SizedBox( height: 32, ), if (_status == _ScanStatus.looking) Text('Looking for a code...', style: Theme.of(context) .textTheme .headline6 ?.copyWith(color: Colors.black)), if (_status == _ScanStatus.success) Text('Found a valid code', style: Theme.of(context) .textTheme .headline6 ?.copyWith(color: Colors.white)), if (_status == _ScanStatus.error) Text('This code is not valid, try again.', style: Theme.of(context) .textTheme .headline6 ?.copyWith(color: Colors.white)), ]), Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ MaterialButton( color: Colors.white38, onPressed: () { Navigator.of(context).pop(''); }, child: const Text('Add manually'), ) ], ) ], )), ]))); } }