/// Écran de capture - Première étape du workflow d'analyse. /// /// Permet de sélectionner le type de cible (concentrique ou silhouette) /// et la source d'image (caméra ou galerie). Affiche un aperçu de l'image /// sélectionnée avant de lancer l'analyse. library; import 'dart:io'; import 'package:flutter/material.dart'; import 'package:image_picker/image_picker.dart'; import '../../core/constants/app_constants.dart'; import '../../core/theme/app_theme.dart'; import '../../data/models/target_type.dart'; import '../analysis/analysis_screen.dart'; import 'widgets/target_type_selector.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; @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text('Nouvelle Analyse'), ), body: SingleChildScrollView( padding: const EdgeInsets.all(AppConstants.defaultPadding), child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ // Target type selection _buildSectionTitle('Type de Cible'), const SizedBox(height: 12), TargetTypeSelector( selectedType: _selectedType, onTypeSelected: (type) { setState(() => _selectedType = type); }, ), const SizedBox(height: AppConstants.largePadding), // Image source selection _buildSectionTitle('Source de l\'Image'), const SizedBox(height: 12), Row( children: [ Expanded( child: ImageSourceButton( icon: Icons.camera_alt, label: 'Camera', onPressed: _isLoading ? null : () => _captureImage(ImageSource.camera), ), ), const SizedBox(width: 12), Expanded( child: ImageSourceButton( icon: Icons.photo_library, label: 'Galerie', onPressed: _isLoading ? null : () => _captureImage(ImageSource.gallery), ), ), ], ), const SizedBox(height: AppConstants.largePadding), // Image preview if (_isLoading) const Center( child: Padding( padding: EdgeInsets.all(32), child: CircularProgressIndicator(), ), ) else if (_selectedImagePath != null) _buildImagePreview(), // Guide text if (_selectedImagePath == null && !_isLoading) _buildGuide(), ], ), ), floatingActionButton: _selectedImagePath != null ? FloatingActionButton.extended( onPressed: _analyzeImage, icon: const Icon(Icons.analytics), label: const Text('Analyser'), ) : null, ); } Widget _buildSectionTitle(String title) { return Text( title, style: Theme.of(context).textTheme.titleMedium?.copyWith( fontWeight: FontWeight.bold, ), ); } Widget _buildImagePreview() { return Column( children: [ _buildSectionTitle('Apercu'), 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.withValues(alpha: 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 centree et visible.', style: TextStyle(color: AppTheme.warningColor.withValues(alpha: 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 entiere dans l\'image'), _buildGuideItem(Icons.wb_sunny, 'Utilisez un bon eclairage'), _buildGuideItem(Icons.straighten, 'Prenez la photo de face'), _buildGuideItem(Icons.blur_off, 'Evitez les images floues'), ], ), ), ); } 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)), ], ), ); } 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) { if (mounted) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text('Erreur lors de la capture: $e'), backgroundColor: AppTheme.errorColor, ), ); } } finally { if (mounted) { setState(() => _isLoading = false); } } } void _analyzeImage() { if (_selectedImagePath == null) return; Navigator.push( context, MaterialPageRoute( builder: (_) => AnalysisScreen( imagePath: _selectedImagePath!, targetType: _selectedType, ), ), ); } }