import 'package:flutter/material.dart'; import 'package:http/http.dart' as http; import '../../core/constants/design_system.dart'; import '../../core/utils/calculate_time_ago.dart'; import '../../data/datasources/social_remote_data_source.dart'; import '../../data/services/secure_storage.dart'; import '../../domain/entities/comment.dart'; import 'animated_widgets.dart'; import 'custom_snackbar.dart'; import 'shimmer_loading.dart'; /// Bottom sheet moderne pour afficher et ajouter des commentaires. /// /// Ce widget affiche tous les commentaires d'un post et permet /// d'ajouter de nouveaux commentaires avec une interface élégante. /// /// **Usage:** /// ```dart /// showCommentsBottomSheet( /// context: context, /// postId: '123', /// ); /// ``` class CommentsBottomSheet extends StatefulWidget { const CommentsBottomSheet({ required this.postId, required this.onCommentAdded, super.key, }); /// ID du post final String postId; /// Callback appelé quand un commentaire est ajouté final VoidCallback onCommentAdded; /// Méthode statique pour afficher le bottom sheet static Future show({ required BuildContext context, required String postId, required VoidCallback onCommentAdded, }) { return showModalBottomSheet( context: context, isScrollControlled: true, backgroundColor: Colors.transparent, builder: (context) => CommentsBottomSheet( postId: postId, onCommentAdded: onCommentAdded, ), ); } @override State createState() => _CommentsBottomSheetState(); } class _CommentsBottomSheetState extends State { final SocialRemoteDataSource _dataSource = SocialRemoteDataSource(http.Client()); final SecureStorage _secureStorage = SecureStorage(); final TextEditingController _commentController = TextEditingController(); final FocusNode _commentFocusNode = FocusNode(); List _comments = []; bool _isLoading = true; bool _isSubmitting = false; String? _currentUserId; @override void initState() { super.initState(); _loadComments(); _loadCurrentUser(); } @override void dispose() { _commentController.dispose(); _commentFocusNode.dispose(); super.dispose(); } Future _loadCurrentUser() async { final userId = await _secureStorage.getUserId(); if (mounted) { setState(() { _currentUserId = userId; }); } } Future _loadComments() async { setState(() { _isLoading = true; }); try { final comments = await _dataSource.getComments(widget.postId); if (mounted) { setState(() { _comments = comments.map((model) => model.toEntity()).toList(); _isLoading = false; }); } } catch (e) { if (mounted) { setState(() { _isLoading = false; }); context.showError('Erreur lors du chargement des commentaires'); } } } Future _submitComment() async { final content = _commentController.text.trim(); if (content.isEmpty) { context.showWarning('Le commentaire ne peut pas être vide'); return; } if (_currentUserId == null) { context.showWarning('Vous devez être connecté pour commenter'); return; } setState(() { _isSubmitting = true; }); try { final newComment = await _dataSource.createComment( postId: widget.postId, content: content, userId: _currentUserId!, ); if (mounted) { setState(() { _comments.insert(0, newComment.toEntity()); _isSubmitting = false; _commentController.clear(); }); _commentFocusNode.unfocus(); widget.onCommentAdded(); context.showSuccess('Commentaire ajouté'); } } catch (e) { if (mounted) { setState(() { _isSubmitting = false; }); context.showError('Erreur lors de l\'ajout du commentaire'); } } } Future _deleteComment(Comment comment, int index) async { try { await _dataSource.deleteComment(widget.postId, comment.id); if (mounted) { setState(() { _comments.removeAt(index); }); widget.onCommentAdded(); context.showSuccess('Commentaire supprimé'); } } catch (e) { if (mounted) { context.showError('Erreur lors de la suppression'); } } } @override Widget build(BuildContext context) { final theme = Theme.of(context); final mediaQuery = MediaQuery.of(context); return Container( height: mediaQuery.size.height * 0.75, decoration: BoxDecoration( color: theme.scaffoldBackgroundColor, borderRadius: const BorderRadius.vertical( top: Radius.circular(DesignSystem.radiusLg), ), ), child: Column( children: [ // Handle bar Container( margin: const EdgeInsets.symmetric(vertical: DesignSystem.spacingSm), width: 36, height: 3, decoration: BoxDecoration( color: theme.colorScheme.onSurface.withOpacity(0.2), borderRadius: BorderRadius.circular(DesignSystem.radiusSm), ), ), // Header Padding( padding: DesignSystem.paddingHorizontal(DesignSystem.spacingLg), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( 'Commentaires', style: theme.textTheme.titleMedium?.copyWith( fontWeight: FontWeight.bold, fontSize: 17, ), ), Text( '${_comments.length}', style: theme.textTheme.bodyMedium?.copyWith( color: theme.colorScheme.onSurface.withOpacity(0.5), fontSize: 14, ), ), ], ), ), Divider( height: 20, thickness: 1, color: theme.dividerColor.withOpacity(0.5), ), // Liste des commentaires Expanded( child: _isLoading ? const SkeletonList( itemCount: 3, skeletonWidget: ListItemSkeleton(), ) : _comments.isEmpty ? Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon( Icons.chat_bubble_outline_rounded, size: 56, color: theme.colorScheme.onSurface.withOpacity(0.2), ), const SizedBox(height: DesignSystem.spacingMd), Text( 'Aucun commentaire', style: theme.textTheme.titleMedium?.copyWith( color: theme.colorScheme.onSurface.withOpacity(0.6), fontSize: 15, fontWeight: FontWeight.w600, ), ), const SizedBox(height: DesignSystem.spacingSm), Text( 'Soyez le premier à commenter', style: theme.textTheme.bodySmall?.copyWith( color: theme.colorScheme.onSurface.withOpacity(0.4), fontSize: 13, ), ), ], ), ) : ListView.separated( padding: DesignSystem.paddingAll(DesignSystem.spacingMd), itemCount: _comments.length, separatorBuilder: (context, index) => Divider( height: 20, thickness: 1, color: theme.dividerColor.withOpacity(0.3), ), itemBuilder: (context, index) { final comment = _comments[index]; return _buildCommentItem(comment, index, theme); }, ), ), // Input pour ajouter un commentaire Container( padding: EdgeInsets.only( left: DesignSystem.spacingLg, right: DesignSystem.spacingLg, top: DesignSystem.spacingMd, bottom: mediaQuery.viewInsets.bottom + DesignSystem.spacingMd, ), decoration: BoxDecoration( color: theme.cardColor, border: Border( top: BorderSide( color: theme.dividerColor.withOpacity(0.3), width: 1, ), ), ), child: SafeArea( child: Row( children: [ Expanded( child: TextField( controller: _commentController, focusNode: _commentFocusNode, decoration: InputDecoration( hintText: 'Ajouter un commentaire...', hintStyle: theme.textTheme.bodyMedium?.copyWith( color: theme.colorScheme.onSurface.withOpacity(0.4), fontSize: 14, ), border: OutlineInputBorder( borderRadius: BorderRadius.circular(DesignSystem.radiusMd), borderSide: BorderSide.none, ), filled: true, fillColor: theme.colorScheme.surfaceVariant.withOpacity(0.5), contentPadding: const EdgeInsets.symmetric( horizontal: DesignSystem.spacingMd, vertical: DesignSystem.spacingSm, ), ), style: theme.textTheme.bodyMedium?.copyWith(fontSize: 14), maxLines: null, textCapitalization: TextCapitalization.sentences, enabled: !_isSubmitting, ), ), const SizedBox(width: DesignSystem.spacingSm), AnimatedScaleButton( onTap: _isSubmitting ? () {} : _submitComment, child: Container( padding: const EdgeInsets.all(DesignSystem.spacingSm), decoration: BoxDecoration( color: _isSubmitting ? theme.colorScheme.primary.withOpacity(0.5) : theme.colorScheme.primary, shape: BoxShape.circle, ), child: _isSubmitting ? SizedBox( width: 18, height: 18, child: CircularProgressIndicator( strokeWidth: 2, color: theme.colorScheme.onPrimary, ), ) : Icon( Icons.send_rounded, color: theme.colorScheme.onPrimary, size: 18, ), ), ), ], ), ), ), ], ), ); } Widget _buildCommentItem(Comment comment, int index, ThemeData theme) { final isCurrentUser = comment.userId == _currentUserId; return FadeInWidget( delay: Duration(milliseconds: index * 50), child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ // Avatar CircleAvatar( radius: 18, backgroundImage: comment.userProfileImageUrl.isNotEmpty ? NetworkImage(comment.userProfileImageUrl) : null, backgroundColor: theme.colorScheme.primary.withOpacity(0.15), child: comment.userProfileImageUrl.isEmpty ? Icon( Icons.person_rounded, color: theme.colorScheme.primary, size: 18, ) : null, ), const SizedBox(width: DesignSystem.spacingMd), // Contenu du commentaire Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // Nom et timestamp Row( children: [ Expanded( child: Text( comment.authorFullName, style: theme.textTheme.bodyMedium?.copyWith( fontWeight: FontWeight.w600, fontSize: 14, ), ), ), Text( calculateTimeAgo(comment.timestamp), style: theme.textTheme.bodySmall?.copyWith( color: theme.colorScheme.onSurface.withOpacity(0.4), fontSize: 12, ), ), ], ), const SizedBox(height: 4), // Contenu Text( comment.content, style: theme.textTheme.bodyMedium?.copyWith( fontSize: 14, height: 1.4, ), ), ], ), ), // Bouton supprimer (si c'est le commentaire de l'utilisateur) if (isCurrentUser) ...[ const SizedBox(width: DesignSystem.spacingSm), IconButton( icon: const Icon(Icons.delete_outline_rounded, size: 18), color: theme.colorScheme.error.withOpacity(0.7), onPressed: () => _deleteComment(comment, index), tooltip: 'Supprimer', padding: EdgeInsets.zero, constraints: const BoxConstraints(minWidth: 32, minHeight: 32), ), ], ], ), ); } }