import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import '../../../core/constants/design_system.dart'; import '../../../domain/entities/social_post.dart'; import '../animated_widgets.dart'; import 'post_media_viewer.dart'; import 'social_action_button.dart'; import 'social_badge.dart'; /// Card modulaire et réutilisable pour afficher un post social. /// /// Utilise des composants atomiques pour une meilleure réutilisabilité. class SocialCardRefactored extends StatefulWidget { const SocialCardRefactored({ required this.post, required this.onLike, required this.onComment, required this.onShare, required this.onDeletePost, required this.onEditPost, this.showVerifiedBadge = false, this.showCategory = false, this.category, super.key, }); final SocialPost post; final VoidCallback onLike; final VoidCallback onComment; final VoidCallback onShare; final VoidCallback onDeletePost; final VoidCallback onEditPost; final bool showVerifiedBadge; final bool showCategory; final String? category; @override State createState() => _SocialCardRefactoredState(); } class _SocialCardRefactoredState extends State { bool _showFullContent = false; @override Widget build(BuildContext context) { final theme = Theme.of(context); return AnimatedCard( margin: const EdgeInsets.only(bottom: DesignSystem.spacingMd), borderRadius: DesignSystem.borderRadiusMd, elevation: 0.5, hoverElevation: 1.5, padding: EdgeInsets.zero, child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // Header _PostHeader( post: widget.post, showVerifiedBadge: widget.showVerifiedBadge, showCategory: widget.showCategory, category: widget.category, onEdit: widget.onEditPost, onDelete: widget.onDeletePost, ), // Médias (si présents) if (widget.post.imageUrl != null && widget.post.imageUrl!.isNotEmpty) PostMediaViewer( medias: [ PostMedia( url: widget.post.imageUrl!, type: MediaType.image, ), ], postId: widget.post.id, onTap: widget.onLike, ), // Contenu et interactions Padding( padding: const EdgeInsets.all(DesignSystem.spacingLg), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // Actions rapides _PostActions( post: widget.post, onLike: widget.onLike, onComment: widget.onComment, onShare: widget.onShare, ), const SizedBox(height: DesignSystem.spacingMd), // Statistiques if (widget.post.likesCount > 0) _PostStats(likesCount: widget.post.likesCount), // Contenu du post _PostContent( post: widget.post, showFullContent: _showFullContent, onToggleFullContent: () { setState(() { _showFullContent = !_showFullContent; }); }, ), // Lien vers les commentaires if (widget.post.commentsCount > 0) _CommentsLink( commentsCount: widget.post.commentsCount, onTap: widget.onComment, ), ], ), ), ], ), ); } } /// Header du post avec avatar, nom, timestamp et menu. class _PostHeader extends StatelessWidget { const _PostHeader({ required this.post, required this.showVerifiedBadge, required this.showCategory, required this.category, required this.onEdit, required this.onDelete, }); final SocialPost post; final bool showVerifiedBadge; final bool showCategory; final String? category; final VoidCallback onEdit; final VoidCallback onDelete; @override Widget build(BuildContext context) { final theme = Theme.of(context); return Padding( padding: const EdgeInsets.fromLTRB( DesignSystem.spacingLg, DesignSystem.spacingMd, DesignSystem.spacingSm, DesignSystem.spacingMd, ), child: Row( children: [ // Avatar _UserAvatar(imageUrl: post.userProfileImageUrl), const SizedBox(width: DesignSystem.spacingMd), // Informations Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ // Nom Flexible( child: Text( post.authorFullName, style: theme.textTheme.bodyMedium?.copyWith( fontWeight: FontWeight.w600, fontSize: 14, letterSpacing: -0.2, ), maxLines: 1, overflow: TextOverflow.ellipsis, ), ), // Badge vérifié if (showVerifiedBadge) ...[ const SizedBox(width: 4), const VerifiedBadge(size: 14), ], // Catégorie if (showCategory && category != null) ...[ const SizedBox(width: 6), CategoryBadge(category: category!), ], ], ), // Timestamp const SizedBox(height: 2), _PostTimestamp(timestamp: post.timestamp), ], ), ), // Menu _PostMenu( onEdit: onEdit, onDelete: onDelete, ), ], ), ); } } /// Avatar utilisateur réutilisable. class _UserAvatar extends StatelessWidget { const _UserAvatar({ required this.imageUrl, this.size = 40, }); final String imageUrl; final double size; @override Widget build(BuildContext context) { final theme = Theme.of(context); final radius = size / 2; return Container( decoration: BoxDecoration( shape: BoxShape.circle, gradient: LinearGradient( colors: [ theme.colorScheme.primary.withOpacity(0.3), theme.colorScheme.secondary.withOpacity(0.3), ], ), boxShadow: [ BoxShadow( color: theme.colorScheme.primary.withOpacity(0.1), blurRadius: 6, offset: const Offset(0, 2), ), ], ), padding: const EdgeInsets.all(2), child: Container( decoration: BoxDecoration( shape: BoxShape.circle, color: theme.scaffoldBackgroundColor, ), padding: const EdgeInsets.all(1.5), child: CircleAvatar( radius: radius - 3.5, backgroundColor: theme.colorScheme.surfaceVariant, backgroundImage: imageUrl.isNotEmpty ? NetworkImage(imageUrl) : null, child: imageUrl.isEmpty ? Icon( Icons.person_rounded, size: radius - 2, color: theme.colorScheme.onSurfaceVariant, ) : null, ), ), ); } } /// Timestamp du post. class _PostTimestamp extends StatelessWidget { const _PostTimestamp({required this.timestamp}); final DateTime timestamp; @override Widget build(BuildContext context) { final theme = Theme.of(context); return Text( _formatTimestamp(timestamp), style: theme.textTheme.bodySmall?.copyWith( color: theme.colorScheme.onSurface.withOpacity(0.5), fontSize: 12, height: 1.2, ), ); } String _formatTimestamp(DateTime timestamp) { final now = DateTime.now(); final difference = now.difference(timestamp); if (difference.inSeconds < 60) return 'À l\'instant'; if (difference.inMinutes < 60) return 'Il y a ${difference.inMinutes}min'; if (difference.inHours < 24) return 'Il y a ${difference.inHours}h'; if (difference.inDays < 7) return 'Il y a ${difference.inDays}j'; if (difference.inDays < 30) { final weeks = (difference.inDays / 7).floor(); return 'Il y a ${weeks}sem'; } return '${timestamp.day}/${timestamp.month}/${timestamp.year}'; } } /// Menu d'options du post. class _PostMenu extends StatelessWidget { const _PostMenu({ required this.onEdit, required this.onDelete, }); final VoidCallback onEdit; final VoidCallback onDelete; @override Widget build(BuildContext context) { return PopupMenuButton( icon: const Icon(Icons.more_horiz_rounded, size: 20), padding: EdgeInsets.zero, iconSize: 20, onSelected: (value) { switch (value) { case 'edit': onEdit(); break; case 'delete': onDelete(); break; } }, itemBuilder: (context) => [ const PopupMenuItem( value: 'edit', child: Row( children: [ Icon(Icons.edit_outlined, size: 18), SizedBox(width: 12), Text('Modifier', style: TextStyle(fontSize: 14)), ], ), ), const PopupMenuItem( value: 'delete', child: Row( children: [ Icon(Icons.delete_outline, size: 18), SizedBox(width: 12), Text('Supprimer', style: TextStyle(fontSize: 14)), ], ), ), ], ); } } /// Actions rapides du post (like, comment, share, bookmark). class _PostActions extends StatefulWidget { const _PostActions({ required this.post, required this.onLike, required this.onComment, required this.onShare, }); final SocialPost post; final VoidCallback onLike; final VoidCallback onComment; final VoidCallback onShare; @override State<_PostActions> createState() => _PostActionsState(); } class _PostActionsState extends State<_PostActions> { bool _isBookmarked = false; @override Widget build(BuildContext context) { final theme = Theme.of(context); return Row( children: [ // Like SocialActionButton( icon: widget.post.isLikedByCurrentUser ? Icons.favorite_rounded : Icons.favorite_border_rounded, onTap: widget.onLike, color: widget.post.isLikedByCurrentUser ? Colors.red : null, tooltip: 'J\'aime', ), const SizedBox(width: DesignSystem.spacingSm), // Comment SocialActionButton( icon: Icons.chat_bubble_outline_rounded, onTap: widget.onComment, tooltip: 'Commenter', ), const SizedBox(width: DesignSystem.spacingSm), // Share SocialActionButton( icon: Icons.send_outlined, onTap: widget.onShare, tooltip: 'Partager', ), const Spacer(), // Bookmark SocialActionButton( icon: _isBookmarked ? Icons.bookmark_rounded : Icons.bookmark_border_rounded, onTap: () { setState(() { _isBookmarked = !_isBookmarked; }); }, color: _isBookmarked ? theme.colorScheme.primary : null, tooltip: 'Enregistrer', ), ], ); } } /// Statistiques du post (nombre de likes). class _PostStats extends StatelessWidget { const _PostStats({required this.likesCount}); final int likesCount; @override Widget build(BuildContext context) { final theme = Theme.of(context); return Padding( padding: const EdgeInsets.only(bottom: DesignSystem.spacingMd), child: Text( likesCount == 1 ? '1 j\'aime' : '$likesCount j\'aime', style: theme.textTheme.labelMedium?.copyWith( fontWeight: FontWeight.w600, fontSize: 13, letterSpacing: -0.1, ), ), ); } } /// Contenu du post avec support des hashtags et mentions. class _PostContent extends StatelessWidget { const _PostContent({ required this.post, required this.showFullContent, required this.onToggleFullContent, }); final SocialPost post; final bool showFullContent; final VoidCallback onToggleFullContent; @override Widget build(BuildContext context) { final theme = Theme.of(context); final content = post.content; final shouldTruncate = content.length > 150 && !showFullContent; return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ RichText( text: TextSpan( style: theme.textTheme.bodyMedium?.copyWith( fontSize: 14, height: 1.4, letterSpacing: -0.1, ), children: [ TextSpan( text: '${post.authorFullName} ', style: const TextStyle(fontWeight: FontWeight.w600), ), ..._buildEnrichedContent( shouldTruncate ? '${content.substring(0, 150)}...' : content, theme, ), ], ), ), if (content.length > 150) GestureDetector( onTap: onToggleFullContent, child: Padding( padding: const EdgeInsets.only(top: 4), child: Text( showFullContent ? 'Voir moins' : 'Voir plus', style: theme.textTheme.bodySmall?.copyWith( color: theme.colorScheme.onSurface.withOpacity(0.5), fontSize: 13, fontWeight: FontWeight.w500, ), ), ), ), ], ); } List _buildEnrichedContent(String content, ThemeData theme) { final spans = []; final words = content.split(' '); for (var i = 0; i < words.length; i++) { final word = words[i]; if (word.startsWith('#') || word.startsWith('@')) { spans.add( TextSpan( text: word, style: TextStyle( color: theme.colorScheme.primary, fontWeight: FontWeight.w600, ), recognizer: TapGestureRecognizer() ..onTap = () { debugPrint('Cliqué sur: $word'); }, ), ); } else { spans.add(TextSpan(text: word)); } if (i < words.length - 1) { spans.add(const TextSpan(text: ' ')); } } return spans; } } /// Lien vers les commentaires. class _CommentsLink extends StatelessWidget { const _CommentsLink({ required this.commentsCount, required this.onTap, }); final int commentsCount; final VoidCallback onTap; @override Widget build(BuildContext context) { final theme = Theme.of(context); return GestureDetector( onTap: onTap, child: Padding( padding: const EdgeInsets.only(top: DesignSystem.spacingMd), child: Text( commentsCount == 1 ? 'Voir le commentaire' : 'Voir les $commentsCount commentaires', style: theme.textTheme.bodySmall?.copyWith( color: theme.colorScheme.onSurface.withOpacity(0.5), fontSize: 13, ), ), ), ); } }