import 'package:flutter/material.dart'; import '../../../../core/widgets/unified_card.dart'; import '../../../../core/theme/app_colors.dart'; import '../../../../core/theme/app_text_styles.dart'; import '../../../../core/utils/date_formatter.dart'; import '../../domain/entities/notification.dart'; /// Widget de carte pour afficher une notification class NotificationCardWidget extends StatelessWidget { final NotificationEntity notification; final VoidCallback? onTap; final VoidCallback? onMarkAsRead; final VoidCallback? onMarkAsImportant; final VoidCallback? onArchive; final VoidCallback? onDelete; final Function(ActionNotification)? onActionTap; const NotificationCardWidget({ super.key, required this.notification, this.onTap, this.onMarkAsRead, this.onMarkAsImportant, this.onArchive, this.onDelete, this.onActionTap, }); @override Widget build(BuildContext context) { final isUnread = !notification.estLue; final isImportant = notification.estImportante; final isExpired = notification.isExpiree; return UnifiedCard( variant: isUnread ? UnifiedCardVariant.elevated : UnifiedCardVariant.outlined, onTap: onTap, child: Container( decoration: BoxDecoration( border: isUnread ? Border.left( color: _getTypeColor(), width: 4, ) : null, ), child: Padding( padding: const EdgeInsets.all(16.0), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // En-tête avec icône, titre et actions Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ // Icône du type de notification Container( width: 40, height: 40, decoration: BoxDecoration( color: _getTypeColor().withOpacity(0.1), borderRadius: BorderRadius.circular(8), ), child: Icon( _getTypeIcon(), color: _getTypeColor(), size: 20, ), ), const SizedBox(width: 12), // Titre et métadonnées Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Expanded( child: Text( notification.titre, style: AppTextStyles.titleSmall.copyWith( fontWeight: isUnread ? FontWeight.w600 : FontWeight.w500, color: isExpired ? AppColors.onSurface.withOpacity(0.6) : AppColors.onSurface, ), maxLines: 2, overflow: TextOverflow.ellipsis, ), ), // Badges de statut if (isImportant) ...[ const SizedBox(width: 8), Icon( Icons.star, color: AppColors.warning, size: 16, ), ], if (isUnread) ...[ const SizedBox(width: 8), Container( width: 8, height: 8, decoration: BoxDecoration( color: AppColors.primary, shape: BoxShape.circle, ), ), ], ], ), const SizedBox(height: 4), // Métadonnées (expéditeur, date) Row( children: [ if (notification.expediteurNom != null) ...[ Text( notification.expediteurNom!, style: AppTextStyles.bodySmall.copyWith( color: AppColors.onSurface.withOpacity(0.7), fontWeight: FontWeight.w500, ), ), Text( ' • ', style: AppTextStyles.bodySmall.copyWith( color: AppColors.onSurface.withOpacity(0.5), ), ), ], Text( notification.tempsEcoule, style: AppTextStyles.bodySmall.copyWith( color: AppColors.onSurface.withOpacity(0.7), ), ), if (isExpired) ...[ Text( ' • Expirée', style: AppTextStyles.bodySmall.copyWith( color: AppColors.error, fontWeight: FontWeight.w500, ), ), ], ], ), ], ), ), // Menu d'actions PopupMenuButton( icon: Icon( Icons.more_vert, color: AppColors.onSurface.withOpacity(0.6), size: 20, ), onSelected: (value) => _handleMenuAction(value), itemBuilder: (context) => [ if (!notification.estLue) const PopupMenuItem( value: 'mark_read', child: ListTile( leading: Icon(Icons.mark_email_read, size: 20), title: Text('Marquer comme lu'), contentPadding: EdgeInsets.zero, ), ), PopupMenuItem( value: 'mark_important', child: ListTile( leading: Icon( notification.estImportante ? Icons.star : Icons.star_border, size: 20, ), title: Text( notification.estImportante ? 'Retirer des importantes' : 'Marquer comme importante', ), contentPadding: EdgeInsets.zero, ), ), if (!notification.estArchivee) const PopupMenuItem( value: 'archive', child: ListTile( leading: Icon(Icons.archive, size: 20), title: Text('Archiver'), contentPadding: EdgeInsets.zero, ), ), const PopupMenuDivider(), const PopupMenuItem( value: 'delete', child: ListTile( leading: Icon(Icons.delete, size: 20, color: Colors.red), title: Text('Supprimer', style: TextStyle(color: Colors.red)), contentPadding: EdgeInsets.zero, ), ), ], ), ], ), const SizedBox(height: 12), // Message de la notification Text( notification.messageAffichage, style: AppTextStyles.bodyMedium.copyWith( color: isExpired ? AppColors.onSurface.withOpacity(0.6) : AppColors.onSurface.withOpacity(0.8), ), maxLines: 3, overflow: TextOverflow.ellipsis, ), // Image de la notification (si présente) if (notification.imageUrl != null) ...[ const SizedBox(height: 12), ClipRRect( borderRadius: BorderRadius.circular(8), child: Image.network( notification.imageUrl!, height: 120, width: double.infinity, fit: BoxFit.cover, errorBuilder: (context, error, stackTrace) => Container( height: 120, decoration: BoxDecoration( color: AppColors.surface, borderRadius: BorderRadius.circular(8), ), child: Icon( Icons.image_not_supported, color: AppColors.onSurface.withOpacity(0.5), ), ), ), ), ], // Actions rapides if (notification.hasActionsRapides) ...[ const SizedBox(height: 16), Wrap( spacing: 8, runSpacing: 8, children: notification.actionsRapidesActives .take(3) // Limite à 3 actions pour éviter l'encombrement .map((action) => _buildActionButton(action)) .toList(), ), ], // Tags (si présents) if (notification.tags != null && notification.tags!.isNotEmpty) ...[ const SizedBox(height: 12), Wrap( spacing: 6, runSpacing: 6, children: notification.tags! .take(3) // Limite à 3 tags .map((tag) => Container( padding: const EdgeInsets.symmetric( horizontal: 8, vertical: 4, ), decoration: BoxDecoration( color: AppColors.surfaceVariant, borderRadius: BorderRadius.circular(12), ), child: Text( tag, style: AppTextStyles.labelSmall.copyWith( color: AppColors.onSurfaceVariant, ), ), )) .toList(), ), ], ], ), ), ), ); } Widget _buildActionButton(ActionNotification action) { return OutlinedButton.icon( onPressed: () => onActionTap?.call(action), icon: Icon( _getActionIcon(action.icone), size: 16, ), label: Text( action.libelle, style: AppTextStyles.labelMedium, ), style: OutlinedButton.styleFrom( padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), minimumSize: Size.zero, tapTargetSize: MaterialTapTargetSize.shrinkWrap, foregroundColor: action.couleur != null ? Color(int.parse(action.couleur!.replaceFirst('#', '0xFF'))) : AppColors.primary, side: BorderSide( color: action.couleur != null ? Color(int.parse(action.couleur!.replaceFirst('#', '0xFF'))) : AppColors.primary, ), ), ); } Color _getTypeColor() { try { return Color(int.parse(notification.couleurType.replaceFirst('#', '0xFF'))); } catch (e) { return AppColors.primary; } } IconData _getTypeIcon() { switch (notification.typeNotification.icone) { case 'event': return Icons.event; case 'payment': return Icons.payment; case 'help': return Icons.help; case 'person_add': return Icons.person_add; case 'cake': return Icons.cake; case 'campaign': return Icons.campaign; case 'mail': return Icons.mail; case 'system_update': return Icons.system_update; case 'build': return Icons.build; case 'schedule': return Icons.schedule; case 'event_busy': return Icons.event_busy; case 'check_circle': return Icons.check_circle; case 'paid': return Icons.paid; case 'error': return Icons.error; case 'thumb_up': return Icons.thumb_up; case 'volunteer_activism': return Icons.volunteer_activism; case 'groups': return Icons.groups; case 'alternate_email': return Icons.alternate_email; default: return Icons.notifications; } } IconData _getActionIcon(String? iconeName) { if (iconeName == null) return Icons.touch_app; switch (iconeName) { case 'visibility': return Icons.visibility; case 'event_available': return Icons.event_available; case 'directions': return Icons.directions; case 'payment': return Icons.payment; case 'schedule': return Icons.schedule; case 'receipt': return Icons.receipt; case 'person': return Icons.person; case 'message': return Icons.message; case 'phone': return Icons.phone; case 'reply': return Icons.reply; default: return Icons.touch_app; } } void _handleMenuAction(String action) { switch (action) { case 'mark_read': onMarkAsRead?.call(); break; case 'mark_important': onMarkAsImportant?.call(); break; case 'archive': onArchive?.call(); break; case 'delete': onDelete?.call(); break; } } }