You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

268 line
9.6KB

  1. // lib/presentation/screens/text_generation/text_generation_screen.dart
  2. import 'dart:convert';
  3. import 'package:flutter/material.dart';// --- CORRECTION 1 : IMPORTER LE REPOSITORY ---
  4. import '../../../repositories/ai_repository.dart';
  5. import '../../../routes/app_routes.dart';
  6. import '../../widgets/creation_flow_layout.dart';
  7. import '../post_refinement/post_refinement_screen.dart'; // Import pour la classe d'arguments
  8. // --- CORRECTION 2 : DÉFINIR UNE CLASSE D'ARGUMENTS PROPRE ---
  9. class TextGenerationScreenArguments {
  10. final String imageBase64;
  11. final AiRepository aiRepository;
  12. TextGenerationScreenArguments({
  13. required this.imageBase64,
  14. required this.aiRepository,
  15. });
  16. }
  17. final class TextGenerationScreen extends StatefulWidget {
  18. // Le constructeur attend maintenant la classe d'arguments.
  19. final TextGenerationScreenArguments arguments;
  20. const TextGenerationScreen({super.key, required this.arguments});
  21. @override
  22. State<TextGenerationScreen> createState() => _TextGenerationScreenState();
  23. }
  24. class _TextGenerationScreenState extends State<TextGenerationScreen> {
  25. // Les variables d'état et contrôleurs restent inchangés
  26. bool _loading = false;
  27. List<String> _generatedIdeas = [];
  28. final List<String> _logs = [];
  29. final _professionController = TextEditingController(text: 'Entrepreneur digital');
  30. final _toneController = TextEditingController(text: 'Professionnel et engageant');
  31. @override
  32. void initState() {
  33. super.initState();
  34. _addLog("🖼️ Image reçue avec succès.");
  35. }
  36. void _addLog(String log) {
  37. if (mounted) {
  38. setState(() => _logs.insert(0, log));
  39. }
  40. }
  41. @override
  42. void dispose() {
  43. _professionController.dispose();
  44. _toneController.dispose();
  45. super.dispose();
  46. }
  47. // --- CORRECTION 3 : UTILISER LE REPOSITORY POUR LA GÉNÉRATION ---
  48. Future<void> _handleGenerateIdeas() async {
  49. if (_loading) {
  50. _addLog("⏳ Annulation : Génération déjà en cours.");
  51. return;
  52. }
  53. if (!mounted) return;
  54. setState(() {
  55. _loading = true;
  56. _generatedIdeas = [];
  57. _logs.clear();
  58. });
  59. _addLog("▶️ Lancement de la génération d'idées...");
  60. try {
  61. final profession = _professionController.text;
  62. final tone = _toneController.text;
  63. _addLog("⚙️ Paramètres : Métier='${profession}', Ton='${tone}'.");
  64. _addLog("🚀 Appel du AiRepository...");
  65. // On appelle la méthode du Repository, qui délègue au bon service.
  66. final ideas = await widget.arguments.aiRepository.generatePostIdeas(
  67. base64Image: widget.arguments.imageBase64,
  68. profession: profession,
  69. tone: tone,
  70. );
  71. if (!mounted) return;
  72. _addLog("✅ Succès ! Réponse reçue.");
  73. if (ideas.isEmpty) {
  74. _addLog("⚠️ Avertissement : Le service a retourné une liste vide.");
  75. } else {
  76. _addLog("📦 ${ideas.length} idée(s) reçue(s).");
  77. }
  78. setState(() {
  79. _generatedIdeas = ideas;
  80. });
  81. _addLog("✨ Interface mise à jour.");
  82. } catch (e) {
  83. if (!mounted) return;
  84. _addLog("❌ ERREUR : ${e.toString()}");
  85. ScaffoldMessenger.of(context).showSnackBar(SnackBar(
  86. content: Text('Erreur: ${e.toString()}'),
  87. backgroundColor: Colors.red));
  88. } finally {
  89. if (mounted) {
  90. setState(() => _loading = false);
  91. }
  92. _addLog("⏹️ Fin du processus.");
  93. }
  94. }
  95. // --- CORRECTION 4 : NAVIGATION PROPRE VERS L'ÉCRAN D'AFFINAGE ---
  96. void _navigateToRefinementScreen(String selectedIdea) {
  97. Navigator.pushNamed(
  98. context,
  99. AppRoutes.postRefinement,
  100. arguments: PostRefinementScreenArguments(
  101. initialText: selectedIdea,
  102. imageBase64: widget.arguments.imageBase64,
  103. aiRepository: widget.arguments.aiRepository, // On passe le Repository
  104. ),
  105. );
  106. }
  107. @override
  108. Widget build(BuildContext context) {
  109. // La structure du build reste la même, mais je corrige l'index de l'étape.
  110. return CreationFlowLayout(
  111. currentStep: 4, // C'est la 5ème étape (index 4)
  112. title: '4. Génération de Texte',
  113. child: Stack(
  114. children: [
  115. SingleChildScrollView(
  116. padding: const EdgeInsets.all(16.0),
  117. child: Column(
  118. crossAxisAlignment: CrossAxisAlignment.start,
  119. children: [
  120. Center(
  121. child: ConstrainedBox(
  122. constraints: const BoxConstraints(maxHeight: 200),
  123. child: ClipRRect(
  124. borderRadius: BorderRadius.circular(12.0),
  125. child: Image.memory(
  126. base64Decode(widget.arguments.imageBase64),
  127. width: double.infinity,
  128. fit: BoxFit.cover,
  129. ),
  130. ),
  131. ),
  132. ),
  133. const SizedBox(height: 24),
  134. TextField(
  135. controller: _professionController,
  136. decoration: const InputDecoration(
  137. labelText: 'Votre métier',
  138. border: OutlineInputBorder(),
  139. prefixIcon: Icon(Icons.work_outline))),
  140. const SizedBox(height: 16),
  141. TextField(
  142. controller: _toneController,
  143. decoration: const InputDecoration(
  144. labelText: 'Ton souhaité',
  145. border: OutlineInputBorder(),
  146. prefixIcon: Icon(Icons.campaign_outlined))),
  147. const SizedBox(height: 24),
  148. SizedBox(
  149. width: double.infinity,
  150. child: FilledButton.icon(
  151. onPressed: _loading ? null : _handleGenerateIdeas,
  152. icon: _loading
  153. ? const SizedBox(
  154. width: 20,
  155. height: 20,
  156. child: CircularProgressIndicator(
  157. color: Colors.white, strokeWidth: 2))
  158. : const Icon(Icons.auto_awesome),
  159. label: const Text('Générer 3 idées de post'),
  160. style: FilledButton.styleFrom(
  161. padding: const EdgeInsets.symmetric(vertical: 16)),
  162. ),
  163. ),
  164. const SizedBox(height: 24),
  165. if (_generatedIdeas.isNotEmpty) ...[
  166. const Divider(height: 32),
  167. Text("Choisissez une idée à affiner",
  168. style: Theme.of(context)
  169. .textTheme
  170. .titleMedium
  171. ?.copyWith(fontWeight: FontWeight.bold)),
  172. const SizedBox(height: 16),
  173. ListView.builder(
  174. shrinkWrap: true,
  175. physics: const NeverScrollableScrollPhysics(),
  176. itemCount: _generatedIdeas.length,
  177. itemBuilder: (context, index) {
  178. final idea = _generatedIdeas[index];
  179. return Card(
  180. margin: const EdgeInsets.only(bottom: 12),
  181. child: ListTile(
  182. title: Text(idea,
  183. maxLines: 3,
  184. overflow: TextOverflow.ellipsis),
  185. leading:
  186. CircleAvatar(child: Text('${index + 1}')),
  187. trailing: const Icon(Icons.edit_note),
  188. onTap: () => _navigateToRefinementScreen(idea)));
  189. },
  190. ),
  191. ],
  192. const SizedBox(height: 150), // Espace pour la console de logs
  193. ],
  194. ),
  195. ),
  196. // La console de logs reste inchangée
  197. Positioned(
  198. bottom: 0,
  199. left: 0,
  200. right: 0,
  201. child: IgnorePointer(
  202. child: Container(
  203. height: 140,
  204. padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
  205. decoration: BoxDecoration(
  206. gradient: LinearGradient(
  207. colors: [
  208. Theme.of(context).scaffoldBackgroundColor.withOpacity(0.0),
  209. Theme.of(context).scaffoldBackgroundColor.withOpacity(0.6),
  210. Theme.of(context).scaffoldBackgroundColor,
  211. ],
  212. begin: Alignment.topCenter,
  213. end: Alignment.bottomCenter,
  214. ),
  215. ),
  216. child: ListView.builder(
  217. reverse: true,
  218. itemCount: _logs.length,
  219. itemBuilder: (context, index) {
  220. final log = _logs[index];
  221. return Text(log, style: TextStyle(
  222. // Logique de couleur conditionnelle pour la lisibilité
  223. color: log.contains('❌')
  224. ? Theme.of(context).colorScheme.error // Rouge pour les erreurs
  225. : (log.contains('✅') || log.contains('📦') || log.contains('✨'))
  226. ? Colors.green[600] // Vert pour les succès
  227. : Theme.of(context)
  228. .colorScheme
  229. .onSurface
  230. .withOpacity(0.8), // Couleur par défaut
  231. fontSize: 12
  232. ),
  233. );
  234. },
  235. ),
  236. ),
  237. ),
  238. ),
  239. ],
  240. ),
  241. );
  242. }
  243. }