import 'package:flutter/material.dart'; import 'package:flutter_svg/svg.dart'; import 'package:lba/gen/assets.gen.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: SvgPicture.asset(Assets.icons.back.path,color: AppColors.textPrimary, width: 28, height: 28), onPressed: () => Navigator.pop(context), ), Text( 'Scan QR Code', style: TextStyle( color: AppColors.textPrimary, fontSize: 18, fontWeight: FontWeight.bold, ), ), IconButton( icon: SvgPicture.asset( _isFlashOn ? Assets.icons.flashSlash.path : Assets.icons.flash.path, color: AppColors.textPrimary, ), 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: SvgPicture.asset( Assets.icons.gallery.path, color: AppColors.surface, ), onPressed: _pickImageFromGallery, ), ), Container( width: 60, height: 60, decoration: BoxDecoration( color: AppColors.surface.withOpacity(0.2), borderRadius: BorderRadius.circular(30), ), child: IconButton( icon: SvgPicture.asset( Assets.icons.keyboard.path, color: AppColors.surface, ), onPressed: () { _showManualInputDialog(); }, ), ), ], ), ), ], ), ); } void _showManualInputDialog() { showDialog( context: context, barrierDismissible: true, builder: (context) { final TextEditingController controller = TextEditingController(); return Dialog( backgroundColor: Colors.transparent, child: Container( padding: const EdgeInsets.all(24), decoration: BoxDecoration( color: AppColors.surface, borderRadius: BorderRadius.circular(20), boxShadow: [ BoxShadow( color: Colors.black.withOpacity(0.15), blurRadius: 20, offset: const Offset(0, 10), ), ], ), child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ // Header with icon and title Row( children: [ Container( padding: const EdgeInsets.all(12), decoration: BoxDecoration( color: AppColors.primary.withOpacity(0.1), borderRadius: BorderRadius.circular(12), ), child: SvgPicture.asset( Assets.icons.keyboard.path, color: AppColors.primary, width: 24, height: 24, ), ), const SizedBox(width: 16), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( 'Manual Entry', style: TextStyle( color: AppColors.textPrimary, fontSize: 20, fontWeight: FontWeight.bold, ), ), const SizedBox(height: 4), Text( 'Enter your code manually', style: TextStyle( color: AppColors.textSecondary, fontSize: 14, ), ), ], ), ), ], ), const SizedBox(height: 24), // Input field with enhanced styling Container( decoration: BoxDecoration( color: AppColors.cardBackground, borderRadius: BorderRadius.circular(12), border: Border.all( color: AppColors.textSecondary.withOpacity(0.2), width: 1, ), ), child: TextField( controller: controller, style: TextStyle( color: AppColors.textPrimary, fontSize: 16, ), maxLines: 3, minLines: 1, decoration: InputDecoration( hintText: 'Enter QR code, barcode, or any text...', hintStyle: TextStyle( color: AppColors.textSecondary.withOpacity(0.7), fontSize: 14, ), border: InputBorder.none, contentPadding: const EdgeInsets.all(16), prefixIcon: Padding( padding: const EdgeInsets.all(16), child: Icon( Icons.qr_code_2, color: AppColors.textSecondary.withOpacity(0.5), size: 20, ), ), ), autofocus: true, textInputAction: TextInputAction.done, onSubmitted: (value) { if (value.trim().isNotEmpty) { Navigator.pop(context); _handleScanResult(value.trim()); } }, ), ), const SizedBox(height: 8), // Helper text Text( 'Tip: You can paste codes from clipboard or type them manually', style: TextStyle( color: AppColors.textSecondary.withOpacity(0.8), fontSize: 12, fontStyle: FontStyle.italic, ), ), const SizedBox(height: 24), // Action buttons with enhanced styling Row( children: [ Expanded( child: Container( height: 48, decoration: BoxDecoration( border: Border.all( color: AppColors.textSecondary.withOpacity(0.3), width: 1, ), borderRadius: BorderRadius.circular(12), ), child: TextButton( onPressed: () => Navigator.pop(context), style: TextButton.styleFrom( foregroundColor: AppColors.textSecondary, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12), ), ), child: const Text( 'Cancel', style: TextStyle( fontSize: 16, fontWeight: FontWeight.w500, ), ), ), ), ), const SizedBox(width: 12), Expanded( child: Container( height: 48, decoration: BoxDecoration( gradient: LinearGradient( colors: [ AppColors.primary, AppColors.primary.withOpacity(0.8), ], begin: Alignment.topLeft, end: Alignment.bottomRight, ), borderRadius: BorderRadius.circular(12), boxShadow: [ BoxShadow( color: AppColors.primary.withOpacity(0.3), blurRadius: 8, offset: const Offset(0, 4), ), ], ), child: ElevatedButton( onPressed: () { if (controller.text.trim().isNotEmpty) { Navigator.pop(context); _handleScanResult(controller.text.trim()); } }, style: ElevatedButton.styleFrom( backgroundColor: Colors.transparent, shadowColor: Colors.transparent, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12), ), ), child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ const Text( 'Submit', style: TextStyle( color: Colors.white, fontSize: 16, fontWeight: FontWeight.w600, ), ), const SizedBox(width: 8), const Icon( Icons.arrow_forward_ios, color: Colors.white, size: 16, ), ], ), ), ), ), ], ), ], ), ), ); }, ); } 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'), ), ], ), ); } }