import 'dart:async'; import 'dart:convert'; import 'dart:io'; import 'dart:typed_data'; import 'package:flutter/material.dart';import 'package:social_content_creator/routes/app_routes.dart'; import 'package:social_content_creator/services/image_editing_service.dart'; // Contrat import 'package:social_content_creator/services/stable_diffusion_service.dart'; // Moteur 1 import 'package:social_content_creator/services/gemini_service.dart'; // Moteur 2 import 'package:image/image.dart' as img; // Énumération pour le choix du moteur d'IA enum ImageEngine { stableDiffusion, gemini } class AiEnhancementScreen extends StatefulWidget { final File image; final String prompt; const AiEnhancementScreen({ super.key, required this.image, required this.prompt, }); @override State createState() => _AiEnhancementScreenState(); } enum GenerationState { idle, generating, done, error } class _AiEnhancementScreenState extends State { GenerationState _generationState = GenerationState.idle; final List _generatedImagesData = []; StreamSubscription? _imageStreamSubscription; // --- NOUVEAUX ÉLÉMENTS --- // 1. Instancier les deux services dans une map final Map _services = { ImageEngine.stableDiffusion: StableDiffusionService(), ImageEngine.gemini: GeminiService(), }; // 2. Garder en mémoire le moteur sélectionné (Stable Diffusion par défaut) ImageEngine _selectedEngine = ImageEngine.stableDiffusion; // --- FIN DES NOUVEAUX ÉLÉMENTS --- Future _generateImageVariations() async { if (_generationState == GenerationState.generating) return; setState(() { _generatedImagesData.clear(); _generationState = GenerationState.generating; }); try { // Préparation de l'image (logique inchangée) final imageBytes = await widget.image.readAsBytes(); final originalImage = img.decodeImage(imageBytes); if (originalImage == null) throw Exception("Impossible de décoder l'image."); final resizedImage = img.copyResize(originalImage, width: 1024); final resizedImageBytes = img.encodeJpg(resizedImage, quality: 90); final imageBase64 = base64Encode(resizedImageBytes); await _imageStreamSubscription?.cancel(); // --- UTILISATION DU SERVICE SÉLECTIONNÉ --- // On récupère le bon service (Stable Diffusion ou Gemini) depuis la map final activeService = _services[_selectedEngine]!; _imageStreamSubscription = activeService.editImage( imageBase64, widget.prompt, resizedImage.width, resizedImage.height, // On demande 3 images à Stable Diffusion, Gemini gèrera ce paramètre numberOfImages: _selectedEngine == ImageEngine.stableDiffusion ? 3 : 1, ).listen( (receivedBase64Image) { 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()}"))); } } 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 void dispose() { _imageStreamSubscription?.cancel(); super.dispose(); } @override Widget build(BuildContext context) { const double bottomButtonHeight = 90.0; return Scaffold( appBar: AppBar(title: const Text("3. Choisir une variation")), body: Stack( children: [ SingleChildScrollView( padding: const EdgeInsets.fromLTRB(16.0, 16.0, 16.0, bottomButtonHeight), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text("Image Originale", style: Theme.of(context).textTheme.titleLarge?.copyWith(fontWeight: FontWeight.bold)), const SizedBox(height: 8), SizedBox( height: 300, width: double.infinity, child: ClipRRect( borderRadius: BorderRadius.circular(12.0), child: Image.file(widget.image, fit: BoxFit.cover), ), ), const SizedBox(height: 16), // --- AJOUT DU SÉLECTEUR DE MOTEUR D'IA --- Container( padding: const EdgeInsets.all(12), decoration: BoxDecoration( color: Theme.of(context).colorScheme.primary.withOpacity(0.05), borderRadius: BorderRadius.circular(12), border: Border.all(color: Theme.of(context).colorScheme.primary.withOpacity(0.2)) ), child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ Text( "Moteur d'IA pour la variation", style: Theme.of(context).textTheme.titleMedium?.copyWith(fontWeight: FontWeight.bold), textAlign: TextAlign.center, ), const SizedBox(height: 12), SegmentedButton( showSelectedIcon: false, segments: const >[ ButtonSegment( value: ImageEngine.stableDiffusion, label: Text('Stable Diffusion'), icon: Icon(Icons.auto_awesome_outlined), ), ButtonSegment( value: ImageEngine.gemini, label: Text('Gemini'), icon: Icon(Icons.bubble_chart_outlined), ), ], selected: {_selectedEngine}, onSelectionChanged: (Set newSelection) { // On ne change de moteur que si la génération n'est pas en cours if (_generationState != GenerationState.generating) { setState(() { _selectedEngine = newSelection.first; }); } }, ), const SizedBox(height: 16), // On garde le prompt de guidage dans le même encart ExpansionTile( title: const Text("Voir le prompt de guidage"), initiallyExpanded: false, tilePadding: EdgeInsets.zero, childrenPadding: const EdgeInsets.only(top: 8), children: [ Text(widget.prompt, style: const TextStyle(fontStyle: FontStyle.italic)), ], ), ], ), ), // --- FIN DE L'AJOUT --- const SizedBox(height: 24), Text("Variations (cliquez pour choisir)", style: Theme.of(context).textTheme.titleLarge?.copyWith(fontWeight: FontWeight.bold)), const SizedBox(height: 16), _buildGeneratedVariationsGrid(), ], ), ), Positioned( bottom: 0, left: 0, right: 0, child: Container( color: Theme.of(context).scaffoldBackgroundColor, padding: const EdgeInsets.all(16.0), child: FilledButton.icon( icon: _generationState == GenerationState.generating ? const SizedBox.shrink() : const Icon(Icons.auto_awesome), label: Text(_generationState == GenerationState.generating ? 'Génération en cours...' : 'Générer à nouveau'), onPressed: _generationState == GenerationState.generating ? null : _generateImageVariations, style: FilledButton.styleFrom(padding: const EdgeInsets.symmetric(vertical: 16)), ), ), ), ], ), ); } // Le reste du fichier est inchangé (_buildGeneratedVariationsGrid) Widget _buildGeneratedVariationsGrid() { // Si la génération est terminée et qu'il n'y a aucune image (cas d'erreur silencieuse) 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)), ); } if (_generationState == GenerationState.idle) { return Container( height: 100, decoration: BoxDecoration(color: Colors.grey.shade200, borderRadius: BorderRadius.circular(8), border: Border.all(color: Colors.grey.shade400, style: BorderStyle.solid)), child: const Center(child: Text("Cliquez sur 'Générer' pour commencer.")), ); } // On affiche 3 slots, même si Gemini n'en remplit qu'un int displayCount = 3; if (_selectedEngine == ImageEngine.gemini && _generationState != GenerationState.generating) { displayCount = _generatedImagesData.length > 0 ? _generatedImagesData.length : 1; } return Row( mainAxisAlignment: MainAxisAlignment.start, children: List.generate(displayCount, (index) { bool hasImage = index < _generatedImagesData.length; return Expanded( child: Padding( padding: const EdgeInsets.symmetric(horizontal: 4.0), child: AspectRatio( aspectRatio: 1.0, child: GestureDetector( onTap: hasImage ? () => _navigateToPreview(index) : null, child: hasImage ? ClipRRect(borderRadius: BorderRadius.circular(8), child: Image.memory(_generatedImagesData[index], fit: BoxFit.cover)) : Container( decoration: BoxDecoration(color: Colors.grey.shade300, borderRadius: BorderRadius.circular(8)), child: _generationState == GenerationState.generating ? const Center(child: CircularProgressIndicator()) : const SizedBox(), ), ), ), ), ); }), ); } }