import 'dart:async'; import 'dart:convert'; import 'dart:io'; import 'dart:ui'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:mobile_scanner/mobile_scanner.dart'; class QRScannerScreen extends StatefulWidget { const QRScannerScreen({super.key}); @override State createState() => _QRScannerScreenState(); } class _QRScannerScreenState extends State { final MobileScannerController controller = MobileScannerController( detectionSpeed: DetectionSpeed.normal, facing: CameraFacing.back, ); @override void dispose() { controller.dispose(); super.dispose(); } DateTime? lastErrorTime; @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text('Scan QR Code'), backgroundColor: Colors.transparent, elevation: 0, ), body: Stack( alignment: Alignment.center, children: [ MobileScanner( controller: controller, onDetect: (capture) { final List barcodes = capture.barcodes; for (final barcode in barcodes) { if (barcode.rawValue != null) { if (barcode.rawValue!.startsWith('ostp://')) { controller.stop(); Navigator.pop(context, barcode.rawValue); return; } else { final now = DateTime.now(); if (lastErrorTime == null || now.difference(lastErrorTime!) > const Duration(seconds: 3)) { lastErrorTime = now; ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Text('Invalid QR Code. Must be an OSTP connection link.'), backgroundColor: Colors.redAccent, duration: Duration(seconds: 2), ), ); } } } } }, ), Container( decoration: ShapeDecoration( shape: QrScannerOverlayShape( borderColor: Theme.of(context).colorScheme.primary, borderRadius: 10, borderLength: 30, borderWidth: 10, cutOutSize: 300, ), ), ), ], ), ); } } class QrScannerOverlayShape extends ShapeBorder { final Color borderColor; final double borderWidth; final double borderRadius; final double borderLength; final double cutOutSize; const QrScannerOverlayShape({ this.borderColor = Colors.red, this.borderWidth = 3.0, this.borderRadius = 0.0, this.borderLength = 20.0, this.cutOutSize = 250.0, }); @override EdgeInsetsGeometry get dimensions => const EdgeInsets.all(10); @override Path getInnerPath(Rect rect, {TextDirection? textDirection}) { return Path() ..fillType = PathFillType.evenOdd ..addPath(getOuterPath(rect), Offset.zero); } @override Path getOuterPath(Rect rect, {TextDirection? textDirection}) { Path path = Path()..addRect(rect); rect = Rect.fromCenter( center: rect.center, width: cutOutSize, height: cutOutSize, ); path.addRect(rect); return path; } @override void paint(Canvas canvas, Rect rect, {TextDirection? textDirection}) { final borderPaint = Paint() ..color = borderColor ..style = PaintingStyle.stroke ..strokeWidth = borderWidth; final backgroundPaint = Paint() ..color = Colors.black54 ..style = PaintingStyle.fill; final cutOutRect = Rect.fromCenter( center: rect.center, width: cutOutSize, height: cutOutSize, ); final backgroundPath = Path() ..addRect(rect) ..addRect(cutOutRect) ..fillType = PathFillType.evenOdd; canvas.drawPath(backgroundPath, backgroundPaint); final path = Path(); // Top left path.moveTo(cutOutRect.left, cutOutRect.top + borderLength); path.lineTo(cutOutRect.left, cutOutRect.top + borderRadius); path.arcToPoint( Offset(cutOutRect.left + borderRadius, cutOutRect.top), radius: Radius.circular(borderRadius), ); path.lineTo(cutOutRect.left + borderLength, cutOutRect.top); // Top right path.moveTo(cutOutRect.right - borderLength, cutOutRect.top); path.lineTo(cutOutRect.right - borderRadius, cutOutRect.top); path.arcToPoint( Offset(cutOutRect.right, cutOutRect.top + borderRadius), radius: Radius.circular(borderRadius), ); path.lineTo(cutOutRect.right, cutOutRect.top + borderLength); // Bottom left path.moveTo(cutOutRect.left, cutOutRect.bottom - borderLength); path.lineTo(cutOutRect.left, cutOutRect.bottom - borderRadius); path.arcToPoint( Offset(cutOutRect.left + borderRadius, cutOutRect.bottom), radius: Radius.circular(borderRadius), clockwise: false, ); path.lineTo(cutOutRect.left + borderLength, cutOutRect.bottom); // Bottom right path.moveTo(cutOutRect.right - borderLength, cutOutRect.bottom); path.lineTo(cutOutRect.right - borderRadius, cutOutRect.bottom); path.arcToPoint( Offset(cutOutRect.right, cutOutRect.bottom - borderRadius), radius: Radius.circular(borderRadius), clockwise: false, ); path.lineTo(cutOutRect.right, cutOutRect.bottom - borderLength); canvas.drawPath(path, borderPaint); // Line in the middle final linePaint = Paint() ..color = borderColor.withOpacity(0.8) ..style = PaintingStyle.stroke ..strokeWidth = 2.0; canvas.drawLine( Offset(cutOutRect.left + 20, cutOutRect.center.dy), Offset(cutOutRect.right - 20, cutOutRect.center.dy), linePaint, ); } @override ShapeBorder scale(double t) { return QrScannerOverlayShape( borderColor: borderColor, borderWidth: borderWidth * t, borderRadius: borderRadius * t, borderLength: borderLength * t, cutOutSize: cutOutSize * t, ); } }