import 'package:flutter/material.dart'; import 'package:http/http.dart' as http; import 'package:image_picker/image_picker.dart'; import '../../../core/constants/design_system.dart'; import '../../../data/services/image_compression_service.dart'; import '../../../data/services/media_upload_service.dart'; import 'media_picker.dart'; /// Dialogue de création de post avec support d'images et vidéos. class CreatePostDialog extends StatefulWidget { const CreatePostDialog({ required this.onPostCreated, this.userAvatarUrl, this.userName, super.key, }); final Future Function(String content, List medias) onPostCreated; final String? userAvatarUrl; final String? userName; @override State createState() => _CreatePostDialogState(); /// Affiche le dialogue de création de post static Future show({ required BuildContext context, required Future Function(String content, List medias) onPostCreated, String? userAvatarUrl, String? userName, }) { return showModalBottomSheet( context: context, isScrollControlled: true, backgroundColor: Colors.transparent, builder: (context) => CreatePostDialog( onPostCreated: onPostCreated, userAvatarUrl: userAvatarUrl, userName: userName, ), ); } } class _CreatePostDialogState extends State { final TextEditingController _contentController = TextEditingController(); final FocusNode _contentFocusNode = FocusNode(); final GlobalKey _formKey = GlobalKey(); late final ImageCompressionService _compressionService; late final MediaUploadService _uploadService; List _selectedMedias = []; bool _isPosting = false; double _uploadProgress = 0.0; String _uploadStatus = ''; @override void initState() { super.initState(); // Initialiser les services _compressionService = ImageCompressionService(); _uploadService = MediaUploadService(http.Client()); // Auto-focus sur le champ de texte Future.delayed(const Duration(milliseconds: 300), () { if (mounted) { _contentFocusNode.requestFocus(); } }); } @override void dispose() { _contentController.dispose(); _contentFocusNode.dispose(); super.dispose(); } @override Widget build(BuildContext context) { final theme = Theme.of(context); final mediaQuery = MediaQuery.of(context); return Container( decoration: BoxDecoration( color: theme.scaffoldBackgroundColor, borderRadius: const BorderRadius.vertical( top: Radius.circular(DesignSystem.radiusLg), ), ), padding: EdgeInsets.only( bottom: mediaQuery.viewInsets.bottom, ), child: SafeArea( child: SingleChildScrollView( child: Padding( padding: const EdgeInsets.all(DesignSystem.spacingLg), child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.stretch, children: [ _buildHeader(theme), const SizedBox(height: DesignSystem.spacingLg), _buildUserInfo(theme), const SizedBox(height: DesignSystem.spacingMd), _buildContentField(theme), const SizedBox(height: DesignSystem.spacingLg), MediaPicker( onMediasChanged: (medias) { setState(() { _selectedMedias = medias; }); }, initialMedias: _selectedMedias, ), const SizedBox(height: DesignSystem.spacingLg), _buildActions(theme), ], ), ), ), ), ); } Widget _buildHeader(ThemeData theme) { return Row( children: [ // Handle Container( width: 40, height: 4, decoration: BoxDecoration( color: theme.colorScheme.onSurface.withOpacity(0.2), borderRadius: BorderRadius.circular(2), ), ), const Spacer(), // Titre Text( 'Créer un post', style: theme.textTheme.titleMedium?.copyWith( fontWeight: FontWeight.bold, fontSize: 17, ), ), const Spacer(), // Bouton fermer IconButton( icon: const Icon(Icons.close_rounded, size: 22), onPressed: () => Navigator.of(context).pop(), padding: EdgeInsets.zero, constraints: const BoxConstraints(minWidth: 40, minHeight: 40), ), ], ); } Widget _buildUserInfo(ThemeData theme) { return Row( children: [ // Avatar CircleAvatar( radius: 20, backgroundColor: theme.colorScheme.primaryContainer, backgroundImage: widget.userAvatarUrl != null && widget.userAvatarUrl!.isNotEmpty ? NetworkImage(widget.userAvatarUrl!) : null, child: widget.userAvatarUrl == null || widget.userAvatarUrl!.isEmpty ? Icon( Icons.person_rounded, size: 24, color: theme.colorScheme.onPrimaryContainer, ) : null, ), const SizedBox(width: DesignSystem.spacingMd), // Nom Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( widget.userName ?? 'Utilisateur', style: theme.textTheme.bodyMedium?.copyWith( fontWeight: FontWeight.w600, fontSize: 14, ), ), const SizedBox(height: 2), Row( children: [ Icon( Icons.public_rounded, size: 14, color: theme.colorScheme.onSurface.withOpacity(0.6), ), const SizedBox(width: 4), Text( 'Public', style: theme.textTheme.bodySmall?.copyWith( fontSize: 12, color: theme.colorScheme.onSurface.withOpacity(0.6), ), ), ], ), ], ), ), ], ); } Widget _buildContentField(ThemeData theme) { return Form( key: _formKey, child: TextFormField( controller: _contentController, focusNode: _contentFocusNode, decoration: InputDecoration( hintText: 'Quoi de neuf ?', hintStyle: theme.textTheme.bodyMedium?.copyWith( color: theme.colorScheme.onSurface.withOpacity(0.4), fontSize: 16, ), border: InputBorder.none, contentPadding: EdgeInsets.zero, ), style: theme.textTheme.bodyMedium?.copyWith( fontSize: 16, height: 1.5, ), maxLines: 8, minLines: 3, maxLength: 500, validator: (value) { if ((value == null || value.trim().isEmpty) && _selectedMedias.isEmpty) { return 'Ajoutez du texte ou des médias'; } return null; }, ), ); } Widget _buildActions(ThemeData theme) { return Column( mainAxisSize: MainAxisSize.min, children: [ // Indicateur de progression lors de l'upload if (_isPosting && _uploadStatus.isNotEmpty) ...[ Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( _uploadStatus, style: theme.textTheme.bodySmall?.copyWith( fontSize: 12, color: theme.colorScheme.primary, fontWeight: FontWeight.w500, ), ), const SizedBox(height: DesignSystem.spacingSm), LinearProgressIndicator( value: _uploadProgress, backgroundColor: theme.colorScheme.surfaceVariant, valueColor: AlwaysStoppedAnimation( theme.colorScheme.primary, ), ), ], ), const SizedBox(height: DesignSystem.spacingMd), ], // Actions Row( children: [ // Indicateur de caractères Expanded( child: Text( '${_contentController.text.length}/500', style: theme.textTheme.bodySmall?.copyWith( fontSize: 12, color: theme.colorScheme.onSurface.withOpacity(0.5), ), ), ), const SizedBox(width: DesignSystem.spacingMd), // Bouton Annuler TextButton( onPressed: _isPosting ? null : () => Navigator.of(context).pop(), child: const Text('Annuler'), ), const SizedBox(width: DesignSystem.spacingSm), // Bouton Publier FilledButton( onPressed: _isPosting ? null : _handlePost, child: _isPosting ? const SizedBox( width: 16, height: 16, child: CircularProgressIndicator( strokeWidth: 2, valueColor: AlwaysStoppedAnimation(Colors.white), ), ) : const Text('Publier'), ), ], ), ], ); } Future _handlePost() async { if (!_formKey.currentState!.validate()) { return; } setState(() { _isPosting = true; _uploadProgress = 0.0; _uploadStatus = 'Préparation...'; }); try { List processedMedias = _selectedMedias; // Étape 1: Compression des images if (_selectedMedias.isNotEmpty) { setState(() { _uploadStatus = 'Compression des images...'; }); processedMedias = await _compressionService.compressMultipleImages( _selectedMedias, config: CompressionConfig.post, onProgress: (processed, total) { if (mounted) { setState(() { _uploadProgress = (processed / total) * 0.5; _uploadStatus = 'Compression $processed/$total...'; }); } }, ); } // Étape 2: Upload des médias if (processedMedias.isNotEmpty) { setState(() { _uploadStatus = 'Upload des médias...'; }); final uploadResults = await _uploadService.uploadMultipleMedias( processedMedias, onProgress: (uploaded, total) { if (mounted) { setState(() { _uploadProgress = 0.5 + (uploaded / total) * 0.5; _uploadStatus = 'Upload $uploaded/$total...'; }); } }, ); // Extraire les URLs des médias uploadés final uploadedMediaUrls = uploadResults.map((result) => result.url).toList(); // Note: Les URLs uploadées sont disponibles dans uploadedMediaUrls. // Pour l'instant, on passe encore les fichiers locaux pour compatibilité, // mais le backend devrait utiliser les URLs uploadées. // À terme, modifier la signature de onPostCreated pour accepter List urls // au lieu de List medias. if (mounted) { debugPrint('[CreatePostDialog] URLs uploadées: $uploadedMediaUrls'); } } // Étape 3: Création du post setState(() { _uploadStatus = 'Création du post...'; _uploadProgress = 1.0; }); await widget.onPostCreated( _contentController.text.trim(), processedMedias, ); // Nettoyer les fichiers temporaires await _compressionService.cleanupTempFiles(); if (mounted) { Navigator.of(context).pop(); } } catch (e) { if (mounted) { final theme = Theme.of(context); ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text('Erreur lors de la publication: ${e.toString()}'), backgroundColor: theme.colorScheme.error, ), ); } } finally { if (mounted) { setState(() { _isPosting = false; _uploadProgress = 0.0; _uploadStatus = ''; }); } } } }