|
- // 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<String> 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<AiEnhancementScreen> createState() => _AiEnhancementScreenState();
- }
-
- class _AiEnhancementScreenState extends State<AiEnhancementScreen> {
- // --- GESTION D'ÉTAT ---
- GenerationState _generationState = GenerationState.idle;
- ImageGenerationEngine? _selectedEngine;
- final List<Uint8List> _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<ImageFilter> _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<void> _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),
- ),
- );
- },
- );
- }
- }
|