// lib/presentation/screens/ai_enhancement/ai_enhancement_screen.dart import 'dart:async'; import 'dart:convert'; import 'dart:io'; import 'dart:typed_data'; import 'package:flutter/material.dart'; import 'package:image/image.dart' as img; import '../../../domain/app_filter.dart'; import '../../../domain/catalogs/filter_catalog.dart'; import '../../../repositories/ai_repository.dart'; // --- AJOUT NÉCESSAIRE POUR LA NAVIGATION --- import '../../../routes/app_routes.dart'; // --- CLASSE D'ARGUMENTS (INCHANGÉE) --- class AiEnhancementScreenArguments { AiEnhancementScreenArguments({ required this.image, required this.initialPrompt, required this.suggestedFilterIds, required this.aiRepository, }); final File image; final String initialPrompt; final List suggestedFilterIds; final AiRepository aiRepository; } // --- ÉNUMÉRATIONS (INCHANGÉES) --- enum GenerationState { idle, generating, done, error } enum ImageGenerationEngine { stableDiffusion, gemini } class AiEnhancementScreen extends StatefulWidget { const AiEnhancementScreen({required this.arguments, super.key}); final AiEnhancementScreenArguments arguments; @override State createState() => _AiEnhancementScreenState(); } class _AiEnhancementScreenState extends State { // --- GESTION D'ÉTAT --- GenerationState _generationState = GenerationState.idle; ImageGenerationEngine? _selectedEngine; final List _generatedImagesData = []; StreamSubscription? _imageStreamSubscription; // --- NOUVEL ÉTAT POUR GÉRER LA VISIBILITÉ DU PROMPT --- bool _isPromptVisible = false; // --- DONNÉES DE L'ÉCRAN --- late final TextEditingController _promptController; late final List _preselectedFilters; @override void initState() { super.initState(); _promptController = TextEditingController(text: widget.arguments.initialPrompt); _preselectedFilters = availableFilters .where((filter) => widget.arguments.suggestedFilterIds.contains(filter.id)) .toList(); } @override void dispose() { _imageStreamSubscription?.cancel(); _promptController.dispose(); super.dispose(); } /// Lance la génération d'images avec le moteur spécifié. Future _startGeneration(ImageGenerationEngine engine) async { if (_generationState == GenerationState.generating) return; setState(() { _generatedImagesData.clear(); _generationState = GenerationState.generating; _selectedEngine = engine; }); try { final imageBytes = await widget.arguments.image.readAsBytes(); final imageBase64 = base64Encode(imageBytes); // --- DÉBUT DE LA CORRECTION --- // 1. Décoder l'image pour obtenir ses dimensions final originalImage = img.decodeImage(imageBytes); if (originalImage == null) { throw Exception("Impossible de lire les dimensions de l'image."); } // 2. Récupérer la largeur et la hauteur final int originalWidth = originalImage.width; final int originalHeight = originalImage.height; // --- FIN DE LA CORRECTION --- await _imageStreamSubscription?.cancel(); _imageStreamSubscription = widget.arguments.aiRepository.editImage( imageBase64, _promptController.text, originalWidth, originalHeight, filtersToApply: _preselectedFilters, ).listen( (receivedBase64Image) { if (!mounted) return; setState(() => _generatedImagesData.add(base64Decode(receivedBase64Image))); }, onError: (error) { if (!mounted) return; setState(() => _generationState = GenerationState.error); ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text('Erreur de génération : $error'))); }, onDone: () { if (!mounted) return; setState(() => _generationState = GenerationState.done); }, ); } catch (e) { if (!mounted) return; setState(() => _generationState = GenerationState.error); ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text('Erreur : ${e.toString()}'))); } } // --- NOUVELLE MÉTHODE POUR NAVIGUER VERS L'APERÇU --- void _navigateToPreview(int index) { if (index >= _generatedImagesData.length) return; final selectedImageData = _generatedImagesData[index]; final imageBase64 = base64Encode(selectedImageData); Navigator.pushNamed(context, AppRoutes.imagePreview, arguments: imageBase64); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('2. Amélioration IA')), body: SingleChildScrollView( padding: const EdgeInsets.all(16.0), child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ _buildEngineChoiceSection(), const SizedBox(height: 24), // --- SECTION DU PROMPT MISE À JOUR --- _buildCollapsiblePromptSection(), const SizedBox(height: 24), Text('Résultats de la génération', style: Theme.of(context).textTheme.titleLarge?.copyWith(fontWeight: FontWeight.bold)), const SizedBox(height: 16), // --- GRILLE DES RÉSULTATS MISE À JOUR --- _buildGeneratedVariationsGrid(), ], ), ), ); } Widget _buildEngineChoiceSection() { return Card( elevation: 2, child: Padding( padding: const EdgeInsets.all(16.0), child: Row( children: [ ClipRRect( borderRadius: BorderRadius.circular(8.0), child: Image.file( widget.arguments.image, width: 100, height: 100, fit: BoxFit.cover, ), ), const SizedBox(width: 16), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ Text('Choisir le moteur IA', style: Theme.of(context).textTheme.titleMedium), const SizedBox(height: 12), FilledButton.icon( onPressed: _generationState == GenerationState.generating ? null : () => _startGeneration(ImageGenerationEngine.stableDiffusion), icon: const Icon(Icons.auto_awesome_outlined), label: const Text('Stable Diffusion'), ), const SizedBox(height: 8), OutlinedButton.icon( onPressed: _generationState == GenerationState.generating ? null : () => _startGeneration(ImageGenerationEngine.gemini), icon: const Icon(Icons.bubble_chart_outlined), label: const Text('Gemini'), ), ], ), ), ], ), ), ); } /// --- NOUVELLE VERSION DE LA SECTION PROMPT --- /// Utilise un ExpansionTile pour être masquable/affichable. Widget _buildCollapsiblePromptSection() { return ExpansionTile( title: Text( 'Prompt de Guidage', style: Theme.of(context).textTheme.titleLarge?.copyWith(fontWeight: FontWeight.bold), ), subtitle: Text( _isPromptVisible ? 'Modifiez le prompt pour affiner le résultat' : 'Afficher pour modifier', style: Theme.of(context).textTheme.bodySmall?.copyWith(color: Colors.grey.shade600), ), onExpansionChanged: (isExpanded) { setState(() => _isPromptVisible = isExpanded); }, children: [ Padding( padding: const EdgeInsets.only(top: 8.0, bottom: 16.0), child: TextField( controller: _promptController, decoration: const InputDecoration( hintText: 'Décrivez l\'image que vous souhaitez obtenir...', border: OutlineInputBorder(), ), maxLines: 4, minLines: 2, ), ), ], ); } /// --- NOUVELLE VERSION DE LA GRILLE DES RÉSULTATS --- /// Utilise un GestureDetector pour rendre chaque image cliquable. Widget _buildGeneratedVariationsGrid() { if (_generationState == GenerationState.idle) { return Container( height: 100, decoration: BoxDecoration(color: Colors.grey.shade200, borderRadius: BorderRadius.circular(8)), child: const Center(child: Text("Choisissez un moteur pour commencer.")), ); } if (_generationState == GenerationState.generating) { return const Padding( padding: EdgeInsets.all(32.0), child: Center(child: CircularProgressIndicator()), ); } if (_generationState == GenerationState.done && _generatedImagesData.isEmpty) { return Container( height: 100, decoration: BoxDecoration(color: Colors.red.shade50, borderRadius: BorderRadius.circular(8), border: Border.all(color: Colors.red.shade200)), child: const Center(child: Text("La génération n'a produit aucune image.", textAlign: TextAlign.center)), ); } return GridView.builder( shrinkWrap: true, physics: const NeverScrollableScrollPhysics(), gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( crossAxisCount: 2, crossAxisSpacing: 8, mainAxisSpacing: 8, ), itemCount: _generatedImagesData.length, itemBuilder: (context, index) { // ON ENTOURE L'IMAGE D'UN GESTUREDETECTOR return GestureDetector( onTap: () => _navigateToPreview(index), child: ClipRRect( borderRadius: BorderRadius.circular(8), child: Image.memory(_generatedImagesData[index], fit: BoxFit.cover), ), ); }, ); } }