import 'dart:io'; import 'package:flutter/material.dart'; import 'package:image_picker/image_picker.dart'; import 'package:permission_handler/permission_handler.dart'; import 'package:google_mlkit_document_scanner/google_mlkit_document_scanner.dart'; import 'package:device_info_plus/device_info_plus.dart'; import '../../core/constants/app_constants.dart'; import '../../core/theme/app_theme.dart'; import '../../data/models/target_type.dart'; import '../crop/crop_screen.dart'; import 'widgets/image_source_button.dart'; class CaptureScreen extends StatefulWidget { const CaptureScreen({super.key}); @override State createState() => _CaptureScreenState(); } class _CaptureScreenState extends State { final ImagePicker _picker = ImagePicker(); TargetType _selectedType = TargetType.concentric; String? _selectedImagePath; bool _isLoading = false; /// Gère la demande de permission et la sélection d'image Future _handleGallerySelection() async { PermissionStatus status; if (Platform.isAndroid) { final deviceInfo = DeviceInfoPlugin(); final androidInfo = await deviceInfo.androidInfo; // Android 13+ (SDK 33) utilise .photos, les versions précédentes utilisent .storage if (androidInfo.version.sdkInt >= 33) { status = await Permission.photos.request(); } else { status = await Permission.storage.request(); } } else { status = await Permission.photos.request(); } if (status.isGranted) { _captureImage(ImageSource.gallery); } else if (status.isPermanentlyDenied) { _showSettingsDialog(); } else { if (mounted) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Text('Accès à la galerie requis pour continuer'), ), ); } } } void _showSettingsDialog() { showDialog( context: context, builder: (context) => AlertDialog( title: const Text('Permission requise'), content: const Text( 'L\'accès aux photos est nécessaire. Veuillez l\'activer dans les paramètres.', ), actions: [ TextButton( onPressed: () => Navigator.pop(context), child: const Text('Annuler'), ), TextButton( onPressed: () => openAppSettings(), child: const Text('Paramètres'), ), ], ), ); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('Source')), body: SingleChildScrollView( padding: const EdgeInsets.all(AppConstants.defaultPadding), child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ const SizedBox(height: AppConstants.largePadding), _buildSectionTitle('Source de l\'Image'), const SizedBox(height: 12), Row( children: [ Expanded( child: ImageSourceButton( icon: Icons.camera_alt, label: 'Scanner', onPressed: _isLoading ? null : _scanDocument, ), ), const SizedBox(width: 12), Expanded( child: ImageSourceButton( icon: Icons.photo_library, label: 'Galerie', onPressed: _isLoading ? null : _handleGallerySelection, // Appelle la nouvelle fonction ), ), ], ), const SizedBox(height: AppConstants.largePadding), if (_isLoading) const Center( child: Padding( padding: EdgeInsets.all(32), child: CircularProgressIndicator(), ), ) else if (_selectedImagePath != null) _buildImagePreview() else _buildGuide(), ], ), ), floatingActionButton: _selectedImagePath != null ? FloatingActionButton.extended( onPressed: _analyzeImage, icon: const Icon(Icons.arrow_forward), label: const Text('Suivant'), ) : null, ); } // Tes widgets _buildSectionTitle, _buildImagePreview, _buildFramingHints restent identiques Widget _buildSectionTitle(String title) { return Text( title, style: Theme.of( context, ).textTheme.titleMedium?.copyWith(fontWeight: FontWeight.bold), ); } Widget _buildImagePreview() { return Column( children: [ _buildSectionTitle('Aperçu'), const SizedBox(height: 12), ClipRRect( borderRadius: BorderRadius.circular(AppConstants.borderRadius), child: Stack( children: [ Image.file( File(_selectedImagePath!), fit: BoxFit.contain, width: double.infinity, ), Positioned( top: 8, right: 8, child: IconButton( icon: const Icon(Icons.close), onPressed: () => setState(() => _selectedImagePath = null), style: IconButton.styleFrom( backgroundColor: Colors.black54, foregroundColor: Colors.white, ), ), ), ], ), ), const SizedBox(height: 12), _buildFramingHints(), ], ); } Widget _buildFramingHints() { return Card( color: AppTheme.warningColor.withOpacity(0.1), child: Padding( padding: const EdgeInsets.all(12), child: Row( children: [ Icon(Icons.info_outline, color: AppTheme.warningColor), const SizedBox(width: 12), Expanded( child: Text( 'Assurez-vous que la cible est bien centrée et visible.', style: TextStyle(color: AppTheme.warningColor.withOpacity(0.8)), ), ), ], ), ), ); } Widget _buildGuide() { return Card( child: Padding( padding: const EdgeInsets.all(AppConstants.defaultPadding), child: Column( children: [ Icon(Icons.help_outline, size: 48, color: Colors.grey[400]), const SizedBox(height: 12), Text( 'Conseils pour une bonne analyse', style: Theme.of( context, ).textTheme.titleSmall?.copyWith(fontWeight: FontWeight.bold), ), const SizedBox(height: 12), _buildGuideItem( Icons.crop_free, 'Cadrez la cible entière dans l\'image', ), _buildGuideItem(Icons.wb_sunny, 'Utilisez un bon éclairage'), _buildGuideItem(Icons.straighten, 'Prenez la photo de face'), _buildGuideItem(Icons.blur_off, 'Évitez les images floues'), _buildGuideItem( Icons.cleaning_services, 'Nettoyer votre objectif avec une chiffonnette pour lunette si nécessaire', ), ], ), ), ); } Widget _buildGuideItem(IconData icon, String text) { return Padding( padding: const EdgeInsets.symmetric(vertical: 4), child: Row( children: [ Icon(icon, size: 20, color: AppTheme.primaryColor), const SizedBox(width: 12), Expanded(child: Text(text)), ], ), ); } // --- LOGIQUE DE CAPTURE --- Future _scanDocument() async { setState(() => _isLoading = true); try { final options = DocumentScannerOptions( documentFormat: DocumentFormat.jpeg, mode: ScannerMode.base, pageLimit: 1, isGalleryImport: false, ); final scanner = DocumentScanner(options: options); final documents = await scanner.scanDocument(); if (documents.images.isNotEmpty) { setState(() => _selectedImagePath = documents.images.first); } } catch (e) { debugPrint('Erreur scan: $e'); } finally { if (mounted) setState(() => _isLoading = false); } } Future _captureImage(ImageSource source) async { setState(() => _isLoading = true); try { final XFile? image = await _picker.pickImage( source: source, maxWidth: 2048, maxHeight: 2048, imageQuality: 90, ); if (image != null) { setState(() => _selectedImagePath = image.path); } } catch (e) { debugPrint('Erreur capture: $e'); } finally { if (mounted) setState(() => _isLoading = false); } } void _analyzeImage() { if (_selectedImagePath == null) return; Navigator.push( context, MaterialPageRoute( builder: (_) => CropScreen( imagePath: _selectedImagePath!, targetType: _selectedType, ), ), ); } }