/// Widget bulle de message v4 — Communication UnionFlow library message_bubble; import 'package:flutter/material.dart'; import 'package:intl/intl.dart'; import '../../../../shared/design_system/unionflow_design_system.dart'; import '../../domain/entities/message.dart'; /// Bulle de message différenciée envoyé/reçu class MessageBubble extends StatelessWidget { final Message message; final bool isMine; final VoidCallback? onLongPress; const MessageBubble({ super.key, required this.message, required this.isMine, this.onLongPress, }); @override Widget build(BuildContext context) { final scheme = Theme.of(context).colorScheme; // Message supprimé if (message.supprime) { return _buildDeleted(scheme); } return Align( alignment: isMine ? Alignment.centerRight : Alignment.centerLeft, child: GestureDetector( onLongPress: onLongPress, child: Container( constraints: BoxConstraints( maxWidth: MediaQuery.of(context).size.width * 0.75, ), margin: EdgeInsets.only( left: isMine ? 48 : 0, right: isMine ? 0 : 48, bottom: SpacingTokens.xs, ), child: Column( crossAxisAlignment: isMine ? CrossAxisAlignment.end : CrossAxisAlignment.start, children: [ // Nom expéditeur (messages reçus) if (!isMine && message.expediteurNomComplet.isNotEmpty) Padding( padding: const EdgeInsets.only(left: SpacingTokens.sm, bottom: 2), child: Text( message.expediteurNomComplet, style: AppTypography.badgeText.copyWith( color: ModuleColors.communication, fontWeight: FontWeight.w600, ), ), ), // Réponse à un message parent if (message.hasParent && message.messageParentApercu != null) Container( margin: EdgeInsets.only( left: isMine ? 0 : 0, bottom: 4, ), padding: const EdgeInsets.symmetric( horizontal: SpacingTokens.sm, vertical: 4, ), decoration: BoxDecoration( color: scheme.surfaceContainerHighest.withOpacity(0.7), borderRadius: BorderRadius.circular(SpacingTokens.radiusSm), border: Border( left: BorderSide( color: ModuleColors.communication, width: 3, ), ), ), child: Text( message.messageParentApercu!, style: AppTypography.badgeText.copyWith( color: scheme.onSurfaceVariant, fontStyle: FontStyle.italic, ), maxLines: 2, overflow: TextOverflow.ellipsis, ), ), // Bulle principale Container( padding: const EdgeInsets.symmetric( horizontal: SpacingTokens.md, vertical: SpacingTokens.sm, ), decoration: BoxDecoration( color: isMine ? ModuleColors.communication : scheme.surfaceContainerHighest, borderRadius: BorderRadius.only( topLeft: const Radius.circular(SpacingTokens.radiusMd), topRight: const Radius.circular(SpacingTokens.radiusMd), bottomLeft: Radius.circular( isMine ? SpacingTokens.radiusMd : SpacingTokens.radiusXs, ), bottomRight: Radius.circular( isMine ? SpacingTokens.radiusXs : SpacingTokens.radiusMd, ), ), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // Contenu selon type _buildContent(scheme), const SizedBox(height: 4), // Horodatage if (message.dateEnvoi != null) Text( DateFormat('HH:mm').format(message.dateEnvoi!), style: AppTypography.badgeText.copyWith( color: isMine ? Colors.white.withOpacity(0.7) : scheme.onSurfaceVariant, fontSize: 10, ), ), ], ), ), ], ), ), ), ); } Widget _buildContent(ColorScheme scheme) { final textColor = isMine ? ModuleColors.communicationOnColor : scheme.onSurface; if (message.isVocal) { return Row( mainAxisSize: MainAxisSize.min, children: [ Icon(Icons.mic, size: 18, color: textColor), const SizedBox(width: 4), Text( 'Note vocale${message.dureeAudio != null ? ' · ${message.dureeAudio}s' : ''}', style: AppTypography.bodyTextSmall.copyWith(color: textColor), ), ], ); } if (message.isImage) { if (message.urlFichier != null) { return ClipRRect( borderRadius: BorderRadius.circular(SpacingTokens.radiusSm), child: Image.network( message.urlFichier!, width: 200, height: 150, fit: BoxFit.cover, errorBuilder: (_, __, ___) => Row( mainAxisSize: MainAxisSize.min, children: [ Icon(Icons.broken_image_outlined, size: 18, color: textColor), const SizedBox(width: 4), Text('Image', style: AppTypography.bodyTextSmall.copyWith(color: textColor)), ], ), ), ); } return Row( mainAxisSize: MainAxisSize.min, children: [ Icon(Icons.image_outlined, size: 18, color: textColor), const SizedBox(width: 4), Text('Image', style: AppTypography.bodyTextSmall.copyWith(color: textColor)), ], ); } if (message.isSysteme) { return Row( mainAxisSize: MainAxisSize.min, children: [ Icon(Icons.info_outline, size: 16, color: textColor.withOpacity(0.7)), const SizedBox(width: 4), Flexible( child: Text( message.contenu ?? 'Notification système', style: AppTypography.bodyTextSmall.copyWith( color: textColor.withOpacity(0.7), fontStyle: FontStyle.italic, ), ), ), ], ); } // TEXTE (défaut) return Text( message.contenu ?? '', style: AppTypography.bodyTextSmall.copyWith(color: textColor), ); } Widget _buildDeleted(ColorScheme scheme) { return Align( alignment: isMine ? Alignment.centerRight : Alignment.centerLeft, child: Container( margin: const EdgeInsets.only(bottom: SpacingTokens.xs), padding: const EdgeInsets.symmetric( horizontal: SpacingTokens.md, vertical: SpacingTokens.sm, ), decoration: BoxDecoration( color: scheme.surfaceContainerHighest.withOpacity(0.4), borderRadius: BorderRadius.circular(SpacingTokens.radiusMd), border: Border.all(color: scheme.outlineVariant.withOpacity(0.3)), ), child: Text( '🚫 Message supprimé', style: AppTypography.bodyTextSmall.copyWith( color: scheme.onSurfaceVariant, fontStyle: FontStyle.italic, ), ), ), ); } }