import 'package:flutter/material.dart'; import 'package:mobile_scanner/mobile_scanner.dart'; import 'package:image_picker/image_picker.dart'; import 'package:permission_handler/permission_handler.dart'; import 'package:lba/res/colors.dart'; import 'package:lba/widgets/app_snackbar.dart'; class QRScannerPage extends StatefulWidget { const QRScannerPage({super.key}); @override State createState() => _QRScannerPageState(); } class _QRScannerPageState extends State { MobileScannerController cameraController = MobileScannerController(); bool _isFlashOn = false; bool _isProcessing = false; @override void initState() { super.initState(); } @override void dispose() { cameraController.dispose(); super.dispose(); } void _toggleFlash() { setState(() { _isFlashOn = !_isFlashOn; }); cameraController.toggleTorch(); } Future _requestStoragePermission() async { final status = await Permission.photos.status; if (status.isGranted) { return true; } if (status.isDenied) { final result = await Permission.photos.request(); return result.isGranted; } if (status.isPermanentlyDenied) { _showPermissionDeniedDialog(); return false; } return false; } void _showPermissionDeniedDialog() { showDialog( context: context, builder: (context) => AlertDialog( title: const Text('Permission Required'), content: const Text( 'This app needs access to your photos to select QR code images. Please enable the permission in Settings.', ), actions: [ TextButton( onPressed: () => Navigator.pop(context), child: const Text('Cancel'), ), ElevatedButton( onPressed: () { Navigator.pop(context); openAppSettings(); }, child: const Text('Open Settings'), ), ], ), ); } Future _pickImageFromGallery() async { final hasPermission = await _requestStoragePermission(); if (!hasPermission) { return; } try { final ImagePicker picker = ImagePicker(); final XFile? image = await picker.pickImage( source: ImageSource.gallery, maxWidth: 1024, maxHeight: 1024, imageQuality: 85, ); if (image != null) { final fileName = image.name; final fileSize = await image.length(); final fileSizeKB = (fileSize / 1024).toStringAsFixed(1); _handleScanResult('Image selected:\nFile: $fileName\nSize: ${fileSizeKB}KB\nPath: ${image.path}'); } } catch (e) { AppSnackBar.showError( context: context, message: 'Error selecting image: $e', ); } } @override Widget build(BuildContext context) { return Scaffold( backgroundColor: AppColors.scaffoldBackground, body: Stack( children: [ MobileScanner( controller: cameraController, onDetect: (capture) { if (_isProcessing) return; final List barcodes = capture.barcodes; if (barcodes.isNotEmpty) { final String code = barcodes.first.rawValue ?? ''; if (code.isNotEmpty) { setState(() { _isProcessing = true; }); _handleScanResult(code); } } }, errorBuilder: (context, error, child) { return Container( width: double.infinity, height: double.infinity, color: AppColors.scaffoldBackground, child: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon( Icons.camera_alt_outlined, color: AppColors.textSecondary, size: 64, ), const SizedBox(height: 16), Text( 'Camera Error', textAlign: TextAlign.center, style: TextStyle( color: AppColors.textPrimary, fontSize: 18, fontWeight: FontWeight.bold, ), ), const SizedBox(height: 8), Text( 'Please check camera permissions', textAlign: TextAlign.center, style: TextStyle( color: AppColors.textSecondary, fontSize: 14, ), ), ], ), ), ); }, ), SafeArea( child: Padding( padding: const EdgeInsets.all(16.0), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ IconButton( icon: Icon( Icons.arrow_back, color: AppColors.textPrimary, size: 28, ), onPressed: () => Navigator.pop(context), ), Text( 'Scan QR Code', style: TextStyle( color: AppColors.textPrimary, fontSize: 18, fontWeight: FontWeight.bold, ), ), IconButton( icon: Icon( _isFlashOn ? Icons.flash_on : Icons.flash_off, color: AppColors.textPrimary, size: 28, ), onPressed: _toggleFlash, ), ], ), ), ), Center( child: SizedBox( width: 250, height: 250, child: Stack( children: [ Container( decoration: BoxDecoration( border: Border.all( color: AppColors.textPrimary, width: 2, ), borderRadius: BorderRadius.circular(12), ), ), Positioned( top: -2, left: -2, child: Container( width: 30, height: 30, decoration: BoxDecoration( border: Border( top: BorderSide(color: AppColors.primary, width: 4), left: BorderSide(color: AppColors.primary, width: 4), ), ), ), ), Positioned( top: -2, right: -2, child: Container( width: 30, height: 30, decoration: BoxDecoration( border: Border( top: BorderSide(color: AppColors.primary, width: 4), right: BorderSide(color: AppColors.primary, width: 4), ), ), ), ), Positioned( bottom: -2, left: -2, child: Container( width: 30, height: 30, decoration: BoxDecoration( border: Border( bottom: BorderSide(color: AppColors.primary, width: 4), left: BorderSide(color: AppColors.primary, width: 4), ), ), ), ), Positioned( bottom: -2, right: -2, child: Container( width: 30, height: 30, decoration: BoxDecoration( border: Border( bottom: BorderSide(color: AppColors.primary, width: 4), right: BorderSide(color: AppColors.primary, width: 4), ), ), ), ), ], ), ), ), Positioned( bottom: 120, left: 0, right: 0, child: Padding( padding: EdgeInsets.symmetric(horizontal: 32.0), child: Text( 'Position the QR code within the frame to scan', textAlign: TextAlign.center, style: TextStyle( color: AppColors.surface, fontSize: 16, fontWeight: FontWeight.w500, ), ), ), ), Positioned( bottom: 50, left: 0, right: 0, child: Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ Container( width: 60, height: 60, decoration: BoxDecoration( color: AppColors.surface.withOpacity(0.2), borderRadius: BorderRadius.circular(30), ), child: IconButton( icon: Icon( Icons.photo_library, color: AppColors.surface, size: 28, ), onPressed: _pickImageFromGallery, ), ), Container( width: 60, height: 60, decoration: BoxDecoration( color: AppColors.surface.withOpacity(0.2), borderRadius: BorderRadius.circular(30), ), child: IconButton( icon: Icon( Icons.keyboard, color: AppColors.surface, size: 28, ), onPressed: () { _showManualInputDialog(); }, ), ), ], ), ), ], ), ); } void _showManualInputDialog() { showDialog( context: context, builder: (context) { final TextEditingController controller = TextEditingController(); return AlertDialog( title: const Text('Enter Code Manually'), content: TextField( controller: controller, decoration: const InputDecoration( hintText: 'Enter QR code or barcode', border: OutlineInputBorder(), ), ), actions: [ TextButton( onPressed: () => Navigator.pop(context), child: const Text('Cancel'), ), ElevatedButton( onPressed: () { if (controller.text.isNotEmpty) { Navigator.pop(context); _handleScanResult(controller.text); } }, child: const Text('Submit'), ), ], ); }, ); } void _handleScanResult(String result) { cameraController.stop(); showDialog( context: context, barrierDismissible: false, builder: (context) => AlertDialog( title: const Text('QR Code Scanned!'), content: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ const Text('Scanned content:', style: TextStyle(fontWeight: FontWeight.bold)), const SizedBox(height: 8), Container( padding: const EdgeInsets.all(12), decoration: BoxDecoration( color: AppColors.cardBackground, borderRadius: BorderRadius.circular(8), ), child: SelectableText( result, style: const TextStyle(fontSize: 14), ), ), ], ), actions: [ TextButton( onPressed: () { Navigator.pop(context); setState(() { _isProcessing = false; }); cameraController.start(); }, child: const Text('Scan Again'), ), ElevatedButton( onPressed: () { Navigator.pop(context); Navigator.pop(context); }, child: const Text('Done'), ), ], ), ); } }