import 'package:camera/camera.dart'; import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'dart:async'; import 'dart:io'; import '../../core/error/exceptions.dart'; class OvalCameraCapturePage extends StatefulWidget { final bool isLogin; final Future Function(File image) onCapture; const OvalCameraCapturePage({ super.key, this.isLogin = true, required this.onCapture, }); @override State createState() => _OvalCameraCapturePageState(); } class _OvalCameraCapturePageState extends State { CameraController? _cameraController; bool _isCameraInitialized = false; String? _errorMessage; bool _isSuccess = false; bool _isLoading = false; Timer? _timer; @override void initState() { super.initState(); _initializeCamera(); } Future _initializeCamera() async { try { setState(() { _errorMessage = null; _isCameraInitialized = false; }); // Dispose existing controller if any await _cameraController?.dispose(); _cameraController = null; // Get available cameras final cameras = await availableCameras(); // Check if cameras list is available if (cameras.isEmpty) { if (!mounted) return; setState(() { _errorMessage = "لا توجد كاميرات متاحة على هذا الجهاز"; _isCameraInitialized = false; }); return; } // Try to find front camera, fallback to first available camera CameraDescription? selectedCamera; try { selectedCamera = cameras.firstWhere( (cam) => cam.lensDirection == CameraLensDirection.front, ); } catch (e) { // If no front camera found, use the first available camera if (cameras.isNotEmpty) { selectedCamera = cameras.first; } else { if (!mounted) return; setState(() { _errorMessage = "لا توجد كاميرات متاحة على هذا الجهاز"; _isCameraInitialized = false; }); return; } } _cameraController = CameraController( selectedCamera, ResolutionPreset.medium, enableAudio: false, imageFormatGroup: ImageFormatGroup.jpeg, ); await _cameraController!.initialize(); if (!mounted) return; setState(() { _isCameraInitialized = true; _errorMessage = null; }); _startScan(); } on CameraException catch (e) { if (!mounted) return; String errorMessage; switch (e.code) { case 'CameraAccessDenied': errorMessage = "تم رفض الوصول إلى الكاميرا. يرجى السماح بالوصول في الإعدادات"; break; case 'CameraAccessDeniedWithoutPrompt': errorMessage = "تم رفض الوصول إلى الكاميرا. يرجى السماح بالوصول في الإعدادات"; break; case 'CameraAccessRestricted': errorMessage = "الوصول إلى الكاميرا مقيد"; break; case 'AudioAccessDenied': errorMessage = "تم رفض الوصول إلى الميكروفون"; break; default: errorMessage = "خطأ في تهيئة الكاميرا: ${e.description ?? 'خطأ غير معروف'}"; } setState(() { _errorMessage = errorMessage; _isCameraInitialized = false; }); } catch (e) { if (!mounted) return; setState(() { _errorMessage = "حدث خطأ غير متوقع أثناء تهيئة الكاميرا"; _isCameraInitialized = false; }); print("Error initializing camera: $e"); } } Future _startScan() async { try { setState(() { _isLoading = true; _errorMessage = null; }); // Simulate scanning delay await Future.delayed(const Duration(seconds: 2)); if (!mounted || _cameraController == null || !_cameraController!.value.isInitialized) { if (mounted) { setState(() { _isLoading = false; _errorMessage = "الكاميرا غير مهيأة. يرجى المحاولة مرة أخرى"; }); } return; } final xFile = await _cameraController!.takePicture(); final file = File(xFile.path); // Check if file exists and is readable if (!await file.exists()) { if (mounted) { setState(() { _isLoading = false; _errorMessage = "فشل حفظ الصورة. يرجى المحاولة مرة أخرى"; }); } return; } // Call the onCapture callback which may throw exceptions await widget.onCapture(file); if (mounted) { setState(() { _isSuccess = true; _isLoading = false; }); // Auto-close after 2 seconds Future.delayed(const Duration(seconds: 2), () { if (mounted) { Navigator.of(context).pop(true); } }); } } on ServerException catch (e) { if (mounted) { setState(() { _isLoading = false; _errorMessage = e.message; }); } } on NetworkException catch (e) { if (mounted) { setState(() { _isLoading = false; _errorMessage = e.message; }); } } on CameraException catch (e) { if (mounted) { String errorMessage; switch (e.code) { case 'captureAlreadyActive': errorMessage = "جاري التقاط صورة بالفعل. يرجى الانتظار"; break; case 'pictureTakingInProgress': errorMessage = "جاري التقاط صورة. يرجى الانتظار"; break; default: errorMessage = "فشل التقاط الصورة: ${e.description ?? 'خطأ غير معروف'}"; } setState(() { _isLoading = false; _errorMessage = errorMessage; }); } } on FileSystemException catch (e) { if (mounted) { setState(() { _isLoading = false; _errorMessage = "فشل حفظ الصورة. يرجى التحقق من مساحة التخزين"; }); } print("File system error: $e"); } catch (e) { if (mounted) { setState(() { _isLoading = false; _errorMessage = "حدث خطأ غير متوقع. يرجى المحاولة مرة أخرى"; }); } print("Unexpected error in _startScan: $e"); } } @override void dispose() { _cameraController?.dispose(); _timer?.cancel(); super.dispose(); } @override Widget build(BuildContext context) { return Scaffold( backgroundColor: Color(0xff000000), body: // Show error screen only if camera failed to initialize _errorMessage != null && !_isCameraInitialized ? Center( child: Padding( padding: const EdgeInsets.all(20.0), child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon( Icons.camera_alt_outlined, size: 64, color: Colors.white70, ), SizedBox(height: 16), Text( _errorMessage!, style: TextStyle( color: Colors.white, fontSize: 18, fontWeight: FontWeight.bold, ), textAlign: TextAlign.center, ), SizedBox(height: 24), Row( mainAxisAlignment: MainAxisAlignment.center, children: [ ElevatedButton( onPressed: _initializeCamera, style: ElevatedButton.styleFrom( backgroundColor: Color(0xffE8001A), foregroundColor: Colors.white, padding: EdgeInsets.symmetric( horizontal: 32, vertical: 12, ), ), child: Text("إعادة المحاولة"), ), SizedBox(width: 16), OutlinedButton( onPressed: () { if (mounted) { Navigator.of(context).pop(false); } }, style: OutlinedButton.styleFrom( foregroundColor: Colors.white, side: BorderSide(color: Colors.white70), padding: EdgeInsets.symmetric( horizontal: 32, vertical: 12, ), ), child: Text("إلغاء"), ), ], ), ], ), ), ) : _isCameraInitialized && _cameraController != null ? Stack( children: [ SizedBox(height: MediaQuery.of(context).size.height), // Camera Preview Positioned( child: Center(child: CameraPreview(_cameraController!)), ), // Oval overlay with dimmed background Positioned.fill( child: CustomPaint(painter: _OvalOverlayPainter()), ), // Top Text Positioned( top: 100, left: 0, right: 0, child: Center( child: Text( widget.isLogin ? "تسجيل الدخول" : "تسجيل خروج", style: TextStyle( color: Colors.white, fontSize: 24, fontWeight: FontWeight.bold, fontFamily: 'Cairo', // Assuming Cairo font based on Arabic text ), ), ), ), // Bottom Text and Logo Positioned( bottom: 80, left: 0, right: 0, child: Column( mainAxisSize: MainAxisSize.min, children: [ Text( _isSuccess ? (widget.isLogin ? "تم تسجيل دخولك بنجاح" : "تم تسجيل خروجك بنجاح") : _isLoading ? (widget.isLogin ? "يتم تسجيل الدخول ..." : "يتم تسجيل الخروج ...") : (widget.isLogin ? "جاهز للتقاط الصورة" : "جاهز للتقاط الصورة"), style: const TextStyle( color: Colors.white, fontSize: 18, fontWeight: FontWeight.bold, ), ), const SizedBox(height: 20), // Logo SizedBox( height: _isSuccess ? 80 : 60, width: _isSuccess ? 80 : 60, child: SvgPicture.asset( _isSuccess ? 'assets/images/logSuccess.svg' : 'assets/images/logLoading.svg', // ignore: deprecated_member_use color: Colors.white, fit: BoxFit.contain, ), ), ], ), ), // Error overlay (shown when error occurs during scanning) if (_errorMessage != null && _isCameraInitialized) Positioned.fill( child: Container( color: Colors.black.withOpacity(0.7), child: Center( child: Padding( padding: const EdgeInsets.all(20.0), child: Column( mainAxisAlignment: MainAxisAlignment.center, mainAxisSize: MainAxisSize.min, children: [ Icon( Icons.error_outline, size: 64, color: Colors.red, ), SizedBox(height: 16), Text( _errorMessage!, style: TextStyle( color: Colors.white, fontSize: 18, fontWeight: FontWeight.bold, ), textAlign: TextAlign.center, ), SizedBox(height: 24), Row( mainAxisAlignment: MainAxisAlignment.center, children: [ ElevatedButton( onPressed: () { setState(() { _errorMessage = null; _isLoading = false; }); _startScan(); }, style: ElevatedButton.styleFrom( backgroundColor: Color(0xffE8001A), foregroundColor: Colors.white, padding: EdgeInsets.symmetric( horizontal: 32, vertical: 12, ), ), child: Text("إعادة المحاولة"), ), SizedBox(width: 16), OutlinedButton( onPressed: () { if (mounted) { Navigator.of(context).pop(false); } }, style: OutlinedButton.styleFrom( foregroundColor: Colors.white, side: BorderSide(color: Colors.white70), padding: EdgeInsets.symmetric( horizontal: 32, vertical: 12, ), ), child: Text("إلغاء"), ), ], ), ], ), ), ), ), ), // // Capture button // Positioned( // bottom: 60, // left: 0, // right: 0, // child: Center( // child: GestureDetector( // onTap: (){}, // child: Container( // width: 72, // height: 72, // decoration: BoxDecoration( // shape: BoxShape.circle, // color: Colors.white, // boxShadow: [ // BoxShadow( // color: Colors.black26, // blurRadius: 8, // offset: Offset(0, 4), // ), // ], // ), // child: Icon(Icons.camera_alt, color: Color(0xffE8001A), size: 36), // ), // ), // ), // ), ], ) : Center( child: CircularProgressIndicator(color: Color(0xffE8001A)), ), ); } } class _OvalOverlayPainter extends CustomPainter { @override void paint(Canvas canvas, Size size) { final width = size.width * 0.75; final height = size.height * 0.4; final center = Offset(size.width / 2, size.height / 2); final ovalRect = Rect.fromCenter( center: center, width: width, height: height, ); // Create a path for the whole screen final screenPath = Path()..addRect(Rect.fromLTWH(0, 0, size.width, size.height)); // Create a path for the oval final ovalPath = Path()..addOval(ovalRect); // Subtract the oval from the screen path final overlayPath = Path.combine( PathOperation.difference, screenPath, ovalPath, ); // Draw the dimmed area outside the oval with gradient final gradient = LinearGradient( begin: Alignment.topCenter, end: Alignment.bottomCenter, colors: [ Color.fromARGB(255, 41, 41, 41), // top dark gray Color.fromARGB(255, 0, 20, 15), // bottom deep green ], ); final paint = Paint() ..shader = gradient.createShader( Rect.fromLTWH(0, 0, size.width, size.height), ) ..style = PaintingStyle.fill; canvas.drawPath(overlayPath, paint); // Draw glowing circles effect (like AppBackground) - drawn after overlay // Top circle - positioned similar to AppBackground (top: -250, left: 100, right: -200) final topCircleCenter = Offset(size.width * 0.3, -250); final topCircleRadius = 150.0; // Draw multiple circles with different opacities for spread effect (spreadRadius: 160) for (int i = 0; i < 5; i++) { final spreadPaint = Paint() ..color = Color.fromARGB(69 ~/ (i + 1), 62, 254, 190) ..maskFilter = MaskFilter.blur(BlurStyle.normal, 140 - (i * 20)); canvas.drawCircle( topCircleCenter, topCircleRadius + (i * 30), spreadPaint, ); } // Bottom circle - positioned similar to AppBackground (bottom: 100, left: -140, right: -120) final bottomCircleCenter = Offset(size.width * 0.2, size.height + 100); final bottomCircleRadius = 160.0; // Draw multiple circles with different opacities for spread effect (spreadRadius: 60) for (int i = 0; i < 5; i++) { final spreadPaint = Paint() ..color = Color.fromARGB(83 ~/ (i + 1), 62, 254, 190) ..maskFilter = MaskFilter.blur(BlurStyle.normal, 180 - (i * 25)); canvas.drawCircle( bottomCircleCenter, bottomCircleRadius + (i * 40), spreadPaint, ); } // Draw oval border final borderPaint = Paint() ..color = Colors.greenAccent ..style = PaintingStyle.stroke ..strokeWidth = 4; canvas.drawOval(ovalRect, borderPaint); } @override bool shouldRepaint(covariant CustomPainter oldDelegate) => false; }