import 'package:flutter/material.dart'; class PostCardWidget extends StatelessWidget { final String title; final String content; final String authorName; final String authorImageUrl; final DateTime publishDate; final List? imageUrls; final int likesCount; final int commentsCount; final int sharesCount; final VoidCallback? onLike; final VoidCallback? onComment; final VoidCallback? onShare; const PostCardWidget({ Key? key, required this.title, required this.content, required this.authorName, required this.authorImageUrl, required this.publishDate, this.imageUrls, this.likesCount = 0, this.commentsCount = 0, this.sharesCount = 0, this.onLike, this.onComment, this.onShare, }) : super(key: key); @override Widget build(BuildContext context) { return Card( margin: const EdgeInsets.only(bottom: 16.0), //elevation: 2, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // En-tête avec auteur et date Padding( padding: const EdgeInsets.all(16.0), child: Row( children: [ // Photo de profil de l'auteur CircleAvatar( radius: 20, backgroundImage: NetworkImage(authorImageUrl), onBackgroundImageError: (_, __) {}, child: authorImageUrl.isEmpty ? const Icon(Icons.person, size: 20) : null, ), const SizedBox(width: 12), // Nom et date Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( authorName, style: Theme.of(context).textTheme.titleLarge?.copyWith( fontWeight: FontWeight.w600, color: Colors.white, ), ), const SizedBox(height: 2), Text( _formatDate(publishDate), style: Theme.of(context).textTheme.bodySmall?.copyWith( color: Colors.grey, ), ), ], ), ), // Menu options IconButton( icon: const Icon(Icons.more_vert, color: Colors.grey), onPressed: () { // Action pour le menu }, ), ], ), ), // Contenu du post Padding( padding: const EdgeInsets.symmetric(horizontal: 16.0), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // Contenu Text( content, style: Theme.of(context).textTheme.bodyMedium?.copyWith( color: Colors.white, height: 1.4, ), ), ], ), ), // Images avec boutons d'action si présentes if (imageUrls != null && imageUrls!.isNotEmpty) Container( //margin: const EdgeInsets.symmetric(vertical: 12.0), height: 200, child: Stack( children: [ // Images PageView.builder( itemCount: imageUrls!.length, itemBuilder: (context, index) { return Container( //margin: const EdgeInsets.symmetric(horizontal: 16.0), decoration: const BoxDecoration( borderRadius: BorderRadius.only( topLeft: Radius.circular(0), topRight: Radius.circular(0), bottomLeft: Radius.circular(8), bottomRight: Radius.circular(8), ), ), child: ClipRRect( borderRadius: const BorderRadius.only( topLeft: Radius.circular(0), topRight: Radius.circular(0), bottomLeft: Radius.circular(8), bottomRight: Radius.circular(8), ), child: _buildImage(imageUrls![index]), ), ); }, ), // Boutons d'action en overlay à droite Positioned( right: 24, top: 0, bottom: 0, child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ _VerticalActionButton( icon: Icons.favorite_outline, count: likesCount, onPressed: onLike, ), const SizedBox(height: 16), _VerticalActionButton( icon: Icons.comment_outlined, count: commentsCount, onPressed: onComment, ), const SizedBox(height: 16), _VerticalActionButton( icon: Icons.share_outlined, count: sharesCount, onPressed: onShare, ), ], ), ), ], ), ), // Si pas d'image, boutons d'action en bas à droite if (imageUrls == null || imageUrls!.isEmpty) Padding( padding: const EdgeInsets.symmetric(vertical: 2.0, horizontal: 16.0), child: Row( mainAxisAlignment: MainAxisAlignment.end, crossAxisAlignment: CrossAxisAlignment.start, children: [ _HorizontalActionButton( icon: Icons.favorite_outline, count: likesCount, onPressed: onLike, ), const SizedBox(width: 16), _HorizontalActionButton( icon: Icons.comment_outlined, count: commentsCount, onPressed: onComment, ), const SizedBox(width: 16), _HorizontalActionButton( icon: Icons.share_outlined, count: sharesCount, onPressed: onShare, ), ], ), ), ], ), ); } String _formatDate(DateTime date) { final now = DateTime.now(); final difference = now.difference(date); if (difference.inDays > 7) { return '${date.day}/${date.month}/${date.year}'; } else if (difference.inDays > 0) { return '${difference.inDays} jour${difference.inDays > 1 ? 's' : ''}'; } else if (difference.inHours > 0) { return '${difference.inHours}h'; } else if (difference.inMinutes > 0) { return '${difference.inMinutes}min'; } else { return 'À l\'instant'; } } Widget _buildImage(String imageUrl) { // Vérifie si c'est une image d'assets ou une URL réseau if (imageUrl.startsWith('assets/')) { return Image.asset( imageUrl, fit: BoxFit.cover, errorBuilder: (context, error, stackTrace) { print('Erreur de chargement d\'asset: $error'); return _buildErrorWidget(); }, ); } else { return Image.network( imageUrl, fit: BoxFit.cover, loadingBuilder: (context, child, loadingProgress) { if (loadingProgress == null) return child; return Container( color: Colors.grey[300], child: Center( child: CircularProgressIndicator( value: loadingProgress.expectedTotalBytes != null ? loadingProgress.cumulativeBytesLoaded / loadingProgress.expectedTotalBytes! : null, ), ), ); }, errorBuilder: (context, error, stackTrace) { print('Erreur de chargement d\'image réseau: $error'); return _buildErrorWidget(); }, ); } } Widget _buildErrorWidget() { return Container( color: Colors.grey[300], child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon( Icons.broken_image, size: 50, color: Colors.grey[600], ), const SizedBox(height: 8), Text( 'Image non disponible', style: TextStyle( color: Colors.grey[600], fontSize: 12, ), ), ], ), ); } } class _VerticalActionButton extends StatefulWidget { final IconData icon; final int? count; final VoidCallback? onPressed; const _VerticalActionButton({ required this.icon, this.count, this.onPressed, }); @override State<_VerticalActionButton> createState() => _VerticalActionButtonState(); } class _VerticalActionButtonState extends State<_VerticalActionButton> with SingleTickerProviderStateMixin { late AnimationController _animationController; late Animation _scaleAnimation; @override void initState() { super.initState(); _animationController = AnimationController( duration: const Duration(milliseconds: 150), vsync: this, ); _scaleAnimation = Tween( begin: 1.0, end: 0.85, ).animate(CurvedAnimation( parent: _animationController, curve: Curves.easeInOut, )); } @override void dispose() { _animationController.dispose(); super.dispose(); } void _handleTap() async { await _animationController.forward(); await _animationController.reverse(); widget.onPressed?.call(); } @override Widget build(BuildContext context) { return GestureDetector( onTap: _handleTap, child: AnimatedBuilder( animation: _scaleAnimation, builder: (context, child) { return Transform.scale( scale: _scaleAnimation.value, child: Container( child: Column( mainAxisSize: MainAxisSize.min, children: [ Container( padding: const EdgeInsets.all(8.0), decoration: BoxDecoration( color: Colors.black.withOpacity(0.6), borderRadius: BorderRadius.circular(20), ), child: Icon( widget.icon, size: 24, color: Colors.white, ), ), if (widget.count != null && widget.count! > 0) ...[ const SizedBox(height: 4), Text( widget.count! > 999 ? '999+' : '${widget.count}', style: const TextStyle( color: Colors.white, fontSize: 12, fontWeight: FontWeight.bold, ), ), ], ], ), ), ); }, ), ); } } class _HorizontalActionButton extends StatefulWidget { final IconData icon; final int? count; final VoidCallback? onPressed; const _HorizontalActionButton({ required this.icon, this.count, this.onPressed, }); @override State<_HorizontalActionButton> createState() => _HorizontalActionButtonState(); } class _HorizontalActionButtonState extends State<_HorizontalActionButton> with SingleTickerProviderStateMixin { late AnimationController _animationController; late Animation _scaleAnimation; @override void initState() { super.initState(); _animationController = AnimationController( duration: const Duration(milliseconds: 150), vsync: this, ); _scaleAnimation = Tween( begin: 1.0, end: 0.85, ).animate(CurvedAnimation( parent: _animationController, curve: Curves.easeInOut, )); } @override void dispose() { _animationController.dispose(); super.dispose(); } void _handleTap() async { await _animationController.forward(); await _animationController.reverse(); widget.onPressed?.call(); } @override Widget build(BuildContext context) { return GestureDetector( onTap: _handleTap, child: AnimatedBuilder( animation: _scaleAnimation, builder: (context, child) { return Transform.scale( scale: _scaleAnimation.value, child: Container( padding: const EdgeInsets.symmetric(horizontal: 12.0, vertical: 8.0), child: Column( mainAxisSize: MainAxisSize.min, mainAxisAlignment: MainAxisAlignment.start, children: [ Container( padding: const EdgeInsets.all(8.0), decoration: BoxDecoration( color: Colors.grey[100], borderRadius: BorderRadius.circular(20), ), child: Icon( widget.icon, size: 20, color: Colors.grey[900], ), ), if (widget.count != null && widget.count! > 0) ...[ const SizedBox(height: 4), Text( widget.count! > 999 ? '999+' : '${widget.count}', style: TextStyle( color: Colors.grey[100], fontSize: 12, fontWeight: FontWeight.w500, ), ), ], ], ), ), ); }, ), ); } }